├── .github └── workflows │ ├── README.md │ ├── cron-daily-fuzz.yml │ ├── cron-weekly-update-nightly.yml │ ├── rust.yml │ └── shellcheck.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo-minimal.lock ├── Cargo-recent.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bitcoind-tests ├── Cargo.toml ├── bin │ └── bitcoind ├── src │ └── main.rs └── tests │ ├── data │ └── random_ms.txt │ ├── setup │ ├── mod.rs │ └── test_util.rs │ ├── test_cpp.rs │ └── test_desc.rs ├── clippy.toml ├── contrib ├── crates.sh ├── integration_test.sh ├── pin.sh ├── test_vars.sh ├── update-lock-files.sh └── whitelist_deps.sh ├── doc ├── ReasoningAboutMultipartyMiniscript.pdf ├── compiler.md ├── resource_limitations.md ├── security_report_2022_04_20.md └── taproot_compiler.pdf ├── embedded ├── Cargo.toml ├── README.md ├── memory.x ├── scripts │ ├── env.sh │ └── install-deps └── src │ └── main.rs ├── examples ├── big.rs ├── htlc.rs ├── parse.rs ├── psbt_sign_finalize.rs ├── sign_multisig.rs ├── taproot.rs ├── taptree_of_horror │ ├── README.md │ ├── helper_fns.rs │ ├── taptree_of_horror.excalidraw │ ├── taptree_of_horror.png │ └── taptree_of_horror.rs ├── verify_tx.rs └── xpub_descriptors.rs ├── fuzz ├── Cargo.toml ├── README.md ├── cycle.sh ├── fuzz-util.sh ├── fuzz.sh ├── fuzz_targets │ ├── compile_descriptor.rs │ ├── compile_taproot.rs │ ├── miniscript_satisfy.rs │ ├── parse_descriptor.rs │ ├── parse_descriptor_priv.rs │ ├── parse_descriptor_secret.rs │ ├── regression_descriptor_parse.rs │ ├── regression_taptree.rs │ ├── roundtrip_concrete.rs │ ├── roundtrip_descriptor.rs │ ├── roundtrip_miniscript_script.rs │ ├── roundtrip_miniscript_script_tap.rs │ ├── roundtrip_miniscript_str.rs │ └── roundtrip_semantic.rs ├── generate-files.sh └── src │ └── lib.rs ├── justfile ├── nightly-version ├── rustfmt.toml ├── src ├── benchmarks.rs ├── blanket_traits.rs ├── descriptor │ ├── bare.rs │ ├── checksum.rs │ ├── key.rs │ ├── mod.rs │ ├── segwitv0.rs │ ├── sh.rs │ ├── sortedmulti.rs │ └── tr │ │ ├── mod.rs │ │ └── taptree.rs ├── error.rs ├── expression │ ├── error.rs │ └── mod.rs ├── interpreter │ ├── error.rs │ ├── inner.rs │ ├── mod.rs │ └── stack.rs ├── iter │ ├── mod.rs │ └── tree.rs ├── lib.rs ├── macros.rs ├── miniscript │ ├── analyzable.rs │ ├── astelem.rs │ ├── context.rs │ ├── decode.rs │ ├── display.rs │ ├── iter.rs │ ├── lex.rs │ ├── limits.rs │ ├── mod.rs │ ├── ms_tests.rs │ ├── satisfy.rs │ └── types │ │ ├── correctness.rs │ │ ├── extra_props.rs │ │ ├── malleability.rs │ │ └── mod.rs ├── plan.rs ├── policy │ ├── compiler.rs │ ├── concrete.rs │ ├── mod.rs │ └── semantic.rs ├── primitives │ ├── absolute_locktime.rs │ ├── mod.rs │ ├── relative_locktime.rs │ └── threshold.rs ├── psbt │ ├── finalizer.rs │ └── mod.rs ├── pub_macros.rs ├── test_utils.rs └── util.rs └── tests └── bip-174.rs /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # rust-miniscript workflow notes 2 | 3 | We are attempting to run max 20 parallel jobs using GitHub actions (usage limit for free tier). 4 | 5 | ref: https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration 6 | 7 | The minimal/recent lock files are handled by CI (`rust.yml`). 8 | 9 | ## Jobs 10 | 11 | Run from `rust.yml` unless stated otherwise. Total 11 jobs. 12 | 13 | 1. `Stable - minimal` 14 | 2. `Stable - recent` 15 | 3. `Nightly - minimal` 16 | 4. `Nightly - recent` 17 | 5. `MSRV - minimal` 18 | 6. `MSRV - recent` 19 | 7. `Lint` 20 | 8. `Docs` 21 | 9. `Docsrs` 22 | 10. `Bench` 23 | 11. `Format` 24 | 12. `Int-tests` 25 | 13. `Embedded` 26 | -------------------------------------------------------------------------------- /.github/workflows/cron-daily-fuzz.yml: -------------------------------------------------------------------------------- 1 | # Automatically generated by fuzz/generate-files.sh 2 | name: Fuzz 3 | on: 4 | schedule: 5 | # 6am every day UTC, this correlates to: 6 | # - 11pm PDT 7 | # - 7am CET 8 | # - 5pm AEDT 9 | - cron: '00 06 * * *' 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 | # We only get 20 jobs at a time, we probably don't want to go 19 | # over that limit with fuzzing because of the hour run time. 20 | fuzz_target: [ 21 | compile_descriptor, 22 | compile_taproot, 23 | miniscript_satisfy, 24 | parse_descriptor, 25 | parse_descriptor_priv, 26 | parse_descriptor_secret, 27 | regression_descriptor_parse, 28 | regression_taptree, 29 | roundtrip_concrete, 30 | roundtrip_descriptor, 31 | roundtrip_miniscript_script, 32 | roundtrip_miniscript_script_tap, 33 | roundtrip_miniscript_str, 34 | roundtrip_semantic, 35 | ] 36 | steps: 37 | - name: Install test dependencies 38 | 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 39 | - uses: actions/checkout@v4 40 | - uses: actions/cache@v4 41 | id: cache-fuzz 42 | with: 43 | path: | 44 | ~/.cargo/bin 45 | fuzz/target 46 | target 47 | key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} 48 | - uses: dtolnay/rust-toolchain@stable 49 | with: 50 | toolchain: '1.65.0' 51 | - name: fuzz 52 | run: cd fuzz && ./fuzz.sh "${{ matrix.fuzz_target }}" 53 | - run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }} 54 | - uses: actions/upload-artifact@v4 55 | with: 56 | name: executed_${{ matrix.fuzz_target }} 57 | path: executed_${{ matrix.fuzz_target }} 58 | 59 | verify-execution: 60 | if: ${{ !github.event.act }} 61 | needs: fuzz 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v2 65 | - uses: actions/download-artifact@v4 66 | - name: Display structure of downloaded files 67 | run: ls -R 68 | - run: find executed_* -type f -exec cat {} + | sort > executed 69 | - run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed 70 | -------------------------------------------------------------------------------- /.github/workflows/cron-weekly-update-nightly.yml: -------------------------------------------------------------------------------- 1 | name: Update Nightly rustc 2 | on: 3 | schedule: 4 | - cron: "5 0 * * 6" # Saturday at 00:05 5 | workflow_dispatch: # allows manual triggering 6 | jobs: 7 | format: 8 | name: Update nightly rustc 9 | runs-on: ubuntu-24.04 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: dtolnay/rust-toolchain@nightly 13 | - name: Update rust.yml to use latest nightly 14 | run: | 15 | set -x 16 | # Not every night has a nightly, so extract the date from whatever 17 | # version of the compiler dtolnay/rust-toolchain gives us. 18 | NIGHTLY_DATE=$(rustc +nightly --verbose --version | sed -ne 's/^commit-date: //p') 19 | # Update the nightly version in the reference file. 20 | echo "nightly-${NIGHTLY_DATE}" > nightly-version 21 | echo "nightly_date=${NIGHTLY_DATE}" >> $GITHUB_ENV 22 | # Some days there is no new nightly. In this case don't make an empty PR. 23 | if ! git diff --exit-code > /dev/null; then 24 | echo "Updated nightly. Opening PR." 25 | echo "changes_made=true" >> $GITHUB_ENV 26 | else 27 | echo "Attempted to update nightly but the latest-nightly date did not change. Not opening any PR." 28 | echo "changes_made=false" >> $GITHUB_ENV 29 | fi 30 | - name: Create Pull Request 31 | if: env.changes_made == 'true' 32 | uses: peter-evans/create-pull-request@v7 33 | with: 34 | token: ${{ secrets.APOELSTRA_CREATE_PR_TOKEN }} 35 | author: Update Nightly Rustc Bot 36 | committer: Update Nightly Rustc Bot 37 | title: Automated daily update to rustc (to nightly-${{ env.nightly_date }}) 38 | body: | 39 | Automated update to Github CI workflow `rust.yml` by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action 40 | commit-message: Automated update to Github CI to rustc nightly-${{ env.nightly_date }} 41 | branch: create-pull-request/daily-nightly-update 42 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | --- # rust-miniscript CI: If you edit this file please update README.md 2 | on: # yamllint disable-line rule:truthy 3 | push: 4 | branches: 5 | - master 6 | - 'test-ci/**' 7 | pull_request: 8 | 9 | name: Continuous integration 10 | 11 | jobs: 12 | Prepare: 13 | runs-on: ubuntu-24.04 14 | outputs: 15 | nightly_version: ${{ steps.read_toolchain.outputs.nightly_version }} 16 | steps: 17 | - name: "Checkout repo" 18 | uses: actions/checkout@v4 19 | - name: "Read nightly version" 20 | id: read_toolchain 21 | run: echo "nightly_version=$(cat nightly-version)" >> $GITHUB_OUTPUT 22 | 23 | Stable: # 2 jobs, one per lock file. 24 | name: Test - stable toolchain 25 | runs-on: ubuntu-latest 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | dep: [minimal, recent] 30 | steps: 31 | - name: "Checkout repo" 32 | uses: actions/checkout@v4 33 | - name: "Checkout maintainer tools" 34 | uses: actions/checkout@v4 35 | with: 36 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 37 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 38 | path: maintainer-tools 39 | - name: "Select toolchain" 40 | uses: dtolnay/rust-toolchain@stable 41 | - name: "Set dependencies" 42 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 43 | - name: "Run test script" 44 | run: ./maintainer-tools/ci/run_task.sh stable 45 | 46 | Nightly: # 2 jobs, one per lock file. 47 | name: Test - nightly toolchain 48 | needs: Prepare 49 | runs-on: ubuntu-latest 50 | strategy: 51 | fail-fast: false 52 | matrix: 53 | dep: [minimal, recent] 54 | steps: 55 | - name: "Checkout repo" 56 | uses: actions/checkout@v4 57 | - name: "Checkout maintainer tools" 58 | uses: actions/checkout@v4 59 | with: 60 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 61 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 62 | path: maintainer-tools 63 | - name: "Select toolchain" 64 | uses: dtolnay/rust-toolchain@v1 65 | with: 66 | toolchain: ${{ needs.Prepare.outputs.nightly_version }} 67 | - name: "Set dependencies" 68 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 69 | - name: "Run test script" 70 | run: ./maintainer-tools/ci/run_task.sh nightly 71 | 72 | MSRV: # 2 jobs, one per lock file. 73 | name: Test - 1.63.0 toolchain 74 | runs-on: ubuntu-latest 75 | strategy: 76 | fail-fast: false 77 | matrix: 78 | dep: [minimal, recent] 79 | steps: 80 | - name: "Checkout repo" 81 | uses: actions/checkout@v4 82 | - name: "Checkout maintainer tools" 83 | uses: actions/checkout@v4 84 | with: 85 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 86 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 87 | path: maintainer-tools 88 | - name: "Select toolchain" 89 | uses: dtolnay/rust-toolchain@stable 90 | with: 91 | toolchain: "1.63.0" 92 | - name: "Set dependencies" 93 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 94 | - name: "Run test script" 95 | run: ./maintainer-tools/ci/run_task.sh msrv 96 | 97 | Lint: 98 | name: Lint - nightly toolchain 99 | needs: Prepare 100 | runs-on: ubuntu-latest 101 | strategy: 102 | fail-fast: false 103 | matrix: 104 | dep: [recent] 105 | steps: 106 | - name: "Checkout repo" 107 | uses: actions/checkout@v4 108 | - name: "Checkout maintainer tools" 109 | uses: actions/checkout@v4 110 | with: 111 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 112 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 113 | path: maintainer-tools 114 | - name: "Select toolchain" 115 | uses: dtolnay/rust-toolchain@v1 116 | with: 117 | toolchain: ${{ needs.Prepare.outputs.nightly_version }} 118 | - name: "Install clippy" 119 | run: rustup component add clippy 120 | - name: "Set dependencies" 121 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 122 | - name: "Run test script" 123 | run: ./maintainer-tools/ci/run_task.sh lint 124 | 125 | Docs: 126 | name: Docs - stable toolchain 127 | runs-on: ubuntu-latest 128 | strategy: 129 | fail-fast: false 130 | matrix: 131 | dep: [recent] 132 | steps: 133 | - name: "Checkout repo" 134 | uses: actions/checkout@v4 135 | - name: "Checkout maintainer tools" 136 | uses: actions/checkout@v4 137 | with: 138 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 139 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 140 | path: maintainer-tools 141 | - name: "Select toolchain" 142 | uses: dtolnay/rust-toolchain@stable 143 | - name: "Set dependencies" 144 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 145 | - name: "Run test script" 146 | run: ./maintainer-tools/ci/run_task.sh docs 147 | 148 | Docsrs: 149 | name: Docs - nightly toolchain 150 | needs: Prepare 151 | runs-on: ubuntu-latest 152 | strategy: 153 | fail-fast: false 154 | matrix: 155 | dep: [recent] 156 | steps: 157 | - name: "Checkout repo" 158 | uses: actions/checkout@v4 159 | - name: "Checkout maintainer tools" 160 | uses: actions/checkout@v4 161 | with: 162 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 163 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 164 | path: maintainer-tools 165 | - name: "Select toolchain" 166 | uses: dtolnay/rust-toolchain@v1 167 | with: 168 | toolchain: ${{ needs.Prepare.outputs.nightly_version }} 169 | - name: "Set dependencies" 170 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 171 | - name: "Run test script" 172 | run: ./maintainer-tools/ci/run_task.sh docsrs 173 | 174 | Bench: 175 | name: Bench - nightly toolchain 176 | needs: Prepare 177 | runs-on: ubuntu-latest 178 | strategy: 179 | fail-fast: false 180 | matrix: 181 | dep: [recent] 182 | steps: 183 | - name: "Checkout repo" 184 | uses: actions/checkout@v4 185 | - name: "Checkout maintainer tools" 186 | uses: actions/checkout@v4 187 | with: 188 | repository: rust-bitcoin/rust-bitcoin-maintainer-tools 189 | ref: c3324024ced9bb1eb854397686919c3ff7d97e1e 190 | path: maintainer-tools 191 | - name: "Select toolchain" 192 | uses: dtolnay/rust-toolchain@v1 193 | with: 194 | toolchain: ${{ needs.Prepare.outputs.nightly_version }} 195 | - name: "Set dependencies" 196 | run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock 197 | - name: "Run test script" 198 | run: ./maintainer-tools/ci/run_task.sh bench 199 | 200 | Format: # 1 job, run cargo fmt directly. 201 | name: Format - nightly toolchain 202 | needs: Prepare 203 | runs-on: ubuntu-latest 204 | strategy: 205 | fail-fast: false 206 | steps: 207 | - name: "Checkout repo" 208 | uses: actions/checkout@v4 209 | - name: "Select toolchain" 210 | uses: dtolnay/rust-toolchain@v1 211 | with: 212 | toolchain: ${{ needs.Prepare.outputs.nightly_version }} 213 | - name: "Install rustfmt" 214 | run: rustup component add rustfmt 215 | - name: "Check formatting" 216 | run: cargo fmt --all -- --check 217 | 218 | Integration: # 1 job for each bitcoind version we support. 219 | name: Integration tests - stable toolchain 220 | runs-on: ubuntu-latest 221 | strategy: 222 | fail-fast: false 223 | matrix: 224 | feature: 225 | [ 226 | "26_0", 227 | "25_2", 228 | "25_1", 229 | "25_0", 230 | "24_2", 231 | "24_1", 232 | "24_0_1", 233 | "23_2", 234 | "23_1", 235 | "23_0", 236 | "22_1", 237 | "22_0", 238 | "0_21_2", 239 | "0_20_2", 240 | "0_19_1", 241 | "0_18_1", 242 | "0_17_1", 243 | ] 244 | steps: 245 | - name: "Checkout repo" 246 | uses: actions/checkout@v4 247 | - name: "Select toolchain" 248 | uses: dtolnay/rust-toolchain@stable 249 | - name: "Run integration tests" 250 | run: cd bitcoind-tests && cargo test --features=${{ matrix.feature }} 251 | 252 | Embedded: 253 | runs-on: ubuntu-latest 254 | steps: 255 | - name: Checkout 256 | uses: actions/checkout@v2 257 | - name: Set up QEMU 258 | run: sudo apt update && sudo apt install -y qemu-system-arm gcc-arm-none-eabi 259 | - name: Checkout Toolchain 260 | uses: actions-rs/toolchain@v1 261 | with: 262 | profile: minimal 263 | toolchain: nightly 264 | override: true 265 | components: rust-src 266 | target: thumbv7m-none-eabi 267 | - name: Run 268 | env: 269 | RUSTFLAGS: "-C link-arg=-Tlink.x" 270 | CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER: "qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" 271 | run: cd embedded && cargo run --target thumbv7m-none-eabi --release 272 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Shellcheck 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | jobs: 8 | shellcheck: 9 | name: Shellcheck 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Run ShellCheck 14 | uses: ludeeus/action-shellcheck@2.0.0 15 | env: 16 | SHELLCHECK_OPTS: -x # allow outside sources 17 | -------------------------------------------------------------------------------- /.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-* -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "miniscript" 3 | version = "13.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 = "2021" 12 | rust-version = "1.63.0" 13 | 14 | [features] 15 | default = ["std"] 16 | std = ["bitcoin/std", "bitcoin/secp-recovery", "bech32/std"] 17 | compiler = [] 18 | trace = [] 19 | 20 | serde = ["dep:serde", "bitcoin/serde"] 21 | rand = ["bitcoin/rand"] 22 | base64 = ["bitcoin/base64"] 23 | 24 | [dependencies] 25 | bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] } 26 | bitcoin = { version = "0.32.0", default-features = false } 27 | 28 | serde = { version = "1.0.103", optional = true } 29 | 30 | [dev-dependencies] 31 | serde_test = "1.0.147" 32 | bitcoin = { version = "0.32.0", features = ["base64"] } 33 | secp256k1 = {version = "0.29.0", features = ["rand-std"]} 34 | 35 | [[example]] 36 | name = "htlc" 37 | required-features = ["std", "compiler"] 38 | 39 | [[example]] 40 | name = "parse" 41 | required-features = ["std"] 42 | 43 | [[example]] 44 | name = "sign_multisig" 45 | required-features = ["std"] 46 | 47 | [[example]] 48 | name = "verify_tx" 49 | required-features = ["std"] 50 | 51 | [[example]] 52 | name = "xpub_descriptors" 53 | required-features = ["std"] 54 | 55 | [[example]] 56 | name = "taproot" 57 | required-features = ["compiler","std"] 58 | 59 | [[example]] 60 | name = "psbt_sign_finalize" 61 | required-features = ["std", "base64"] 62 | 63 | [[example]] 64 | name = "big" 65 | required-features = ["std", "base64", "compiler"] 66 | 67 | [[example]] 68 | name = "taptree_of_horror" 69 | path = "examples/taptree_of_horror/taptree_of_horror.rs" 70 | required-features = ["compiler"] 71 | 72 | [workspace] 73 | members = ["fuzz"] 74 | exclude = ["embedded", "bitcoind-tests"] 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stars](https://img.shields.io/github/stars/rust-bitcoin/rust-miniscript)](https://github.com/rust-bitcoin/rust-miniscript/stargazers) 2 | [![Forks](https://img.shields.io/github/forks/rust-bitcoin/rust-miniscript)](https://github.com/rust-bitcoin/rust-miniscript/network/members) 3 | [![Contributors](https://img.shields.io/github/contributors/rust-bitcoin/rust-miniscript)](https://github.com/rust-bitcoin/rust-miniscript/graphs/contributors) 4 | [![Build](https://github.com/rust-bitcoin/rust-miniscript/workflows/Continuous%20integration/badge.svg)](https://github.com/rust-bitcoin/rust-miniscript/actions) 5 | [![Issues](https://img.shields.io/github/issues-raw/rust-bitcoin/rust-miniscript)](https://github.com/rust-bitcoin/rust-miniscript/issues) 6 | 7 | **Minimum Supported Rust Version:** 1.63.0 8 | 9 | # Miniscript 10 | 11 | Library for handling [Miniscript](http://bitcoin.sipa.be/miniscript/), 12 | which is a subset of Bitcoin Script designed to support simple and general 13 | tooling. Miniscripts represent threshold circuits of spending conditions, 14 | and can therefore be easily visualized or serialized as human-readable 15 | strings. 16 | 17 | ## High-Level Features 18 | 19 | This library supports 20 | 21 | * [Output descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) 22 | including embedded Miniscripts 23 | * Parsing and serializing descriptors to a human-readable string format 24 | * Compilation of abstract spending policies to Miniscript (enabled by the 25 | `compiler` flag) 26 | * Semantic analysis of Miniscripts and spending policies, with user-defined 27 | public key types 28 | * Encoding and decoding Miniscript as Bitcoin Script, given key types that 29 | are convertible to `bitcoin::PublicKey` 30 | * Determining satisfiability, and optimal witnesses, for a given descriptor; 31 | completing an unsigned `bitcoin::TxIn` with appropriate data 32 | * Determining the specific keys, hash preimages and timelocks used to spend 33 | coins in a given Bitcoin transaction 34 | * `no_std` support enabled by disabling the `default-features`. See `embedded/` for an example. 35 | 36 | More information can be found in [the documentation](https://docs.rs/miniscript) 37 | or in [the `examples/` directory](https://github.com/rust-bitcoin/rust-miniscript/tree/master/examples) 38 | 39 | ## Minimum Supported Rust Version (MSRV) 40 | 41 | This library should always compile with any combination of features on **Rust 1.63.0**. 42 | 43 | Some dependencies do not play nicely with our MSRV, if you are running the tests 44 | you may need to pin some dependencies. See `./contrib/pin.sh` for current pinning. 45 | 46 | ## Contributing 47 | 48 | Contributions are generally welcome. If you intend to make larger changes please 49 | discuss them in an issue before PRing them to avoid duplicate work and 50 | architectural mismatches. If you have any questions or ideas you want to discuss 51 | please join us in 52 | [##miniscript](https://web.libera.chat/?channels=##miniscript) on Libera. 53 | 54 | ## Benchmarks 55 | 56 | We use a custom Rust compiler configuration conditional to guard the bench mark code. To run the 57 | benchmarks use: `RUSTFLAGS='--cfg=bench' cargo +nightly bench benchmarks`. 58 | 59 | 60 | ## Release Notes 61 | 62 | See [CHANGELOG.md](CHANGELOG.md). 63 | 64 | 65 | ## Licensing 66 | 67 | The code in this project is licensed under the [Creative Commons CC0 1.0 68 | Universal license](LICENSE). We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX 69 | IDs](https://spdx.dev/ids/). 70 | -------------------------------------------------------------------------------- /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 = { package = "corepc-node", version = "0.4.0", default-features = false } 13 | actual-rand = { package = "rand", version = "0.8.4"} 14 | secp256k1 = {version = "0.29.0", features = ["rand-std"]} 15 | 16 | [features] 17 | # Enable the same feature in `bitcoind`. 18 | "26_0" = ["bitcoind/26_0"] 19 | "25_2" = ["bitcoind/25_2"] 20 | "25_1" = ["bitcoind/25_1"] 21 | "25_0" = ["bitcoind/25_0"] 22 | "24_2" = ["bitcoind/24_2"] 23 | "24_1" = ["bitcoind/24_1"] 24 | "24_0_1" = ["bitcoind/24_0_1"] 25 | "23_2" = ["bitcoind/23_2"] 26 | "23_1" = ["bitcoind/23_1"] 27 | "23_0" = ["bitcoind/23_0"] 28 | "22_1" = ["bitcoind/22_1"] 29 | "22_0" = ["bitcoind/22_0"] 30 | "0_21_2" = ["bitcoind/0_21_2"] 31 | "0_20_2" = ["bitcoind/0_20_2"] 32 | "0_19_1" = ["bitcoind/0_19_1"] 33 | "0_18_1" = ["bitcoind/0_18_1"] 34 | "0_17_1" = ["bitcoind/0_17_1"] 35 | -------------------------------------------------------------------------------- /bitcoind-tests/bin/bitcoind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-bitcoin/rust-miniscript/1c4e95998bb325616ab0f1a3c47240b12c5a95df/bitcoind-tests/bin/bitcoind -------------------------------------------------------------------------------- /bitcoind-tests/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /bitcoind-tests/tests/setup/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate miniscript; 2 | 3 | use bitcoind::client::bitcoin; 4 | 5 | pub mod test_util; 6 | 7 | // Launch an instance of bitcoind with 8 | pub fn setup() -> bitcoind::BitcoinD { 9 | // Create env var BITCOIND_EXE_PATH to point to the ../bitcoind/bin/bitcoind binary 10 | let key = "BITCOIND_EXE"; 11 | if std::env::var(key).is_err() { 12 | let mut root_path = std::env::current_dir().unwrap(); 13 | while std::fs::metadata(root_path.join("LICENSE")).is_err() { 14 | if !root_path.pop() { 15 | panic!("Could not find LICENSE file; do not know where repo root is."); 16 | } 17 | } 18 | 19 | let bitcoind_path = root_path 20 | .join("bitcoind-tests") 21 | .join("bin") 22 | .join("bitcoind"); 23 | std::env::set_var(key, bitcoind_path); 24 | } 25 | 26 | let exe_path = bitcoind::exe_path().unwrap(); 27 | let bitcoind = bitcoind::BitcoinD::new(exe_path).unwrap(); 28 | let cl = &bitcoind.client; 29 | // generate to an address by the wallet. And wait for funds to mature 30 | let addr = cl.new_address().unwrap(); 31 | let blks = cl.generate_to_address(101, &addr).unwrap(); 32 | assert_eq!(blks.0.len(), 101); 33 | 34 | let balance = cl 35 | .get_balance() 36 | .expect("failed to get balance") 37 | .balance() 38 | .unwrap(); 39 | assert_eq!(balance, bitcoin::Amount::from_sat(100_000_000 * 50)); 40 | bitcoind 41 | } 42 | -------------------------------------------------------------------------------- /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::{AddressType, Client}; 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 model = cl 56 | .get_transaction(txid) 57 | .expect("rpc call failed") 58 | .into_model() 59 | .expect("conversion to model type failed"); 60 | let tx = model.tx; 61 | 62 | for (i, txout) in tx.output.into_iter().enumerate() { 63 | if txout.value == value { 64 | return (OutPoint::new(txid, i as u32), txout); 65 | } 66 | } 67 | unreachable!("Only call get vout on functions which have the expected outpoint"); 68 | } 69 | 70 | pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { 71 | let secp = secp256k1::Secp256k1::new(); 72 | let desc_vec = parse_miniscripts(&testdata.pubdata); 73 | let sks = &testdata.secretdata.sks; 74 | let pks = &testdata.pubdata.pks; 75 | // Generate some blocks 76 | let blocks = cl 77 | .generate_to_address(500, &cl.new_address().unwrap()) 78 | .unwrap(); 79 | assert_eq!(blocks.0.len(), 500); 80 | 81 | // Next send some btc to each address corresponding to the miniscript 82 | let mut txids = vec![]; 83 | for wsh in desc_vec.iter() { 84 | let txid = cl 85 | .send_to_address(&wsh.address(bitcoin::Network::Regtest).unwrap(), btc(1)) 86 | .expect("rpc call failed") 87 | .txid() 88 | .expect("conversion to model failed"); 89 | txids.push(txid); 90 | } 91 | // Wait for the funds to mature. 92 | let blocks = cl 93 | .generate_to_address(50, &cl.new_address().unwrap()) 94 | .unwrap(); 95 | assert_eq!(blocks.0.len(), 50); 96 | // Create a PSBT for each transaction. 97 | // Spend one input and spend one output for simplicity. 98 | let mut psbts = vec![]; 99 | for (desc, txid) in desc_vec.iter().zip(txids) { 100 | let mut psbt = Psbt { 101 | unsigned_tx: Transaction { 102 | version: transaction::Version::TWO, 103 | lock_time: absolute::LockTime::from_time(1_603_866_330).expect("valid timestamp"), // 10/28/2020 @ 6:25am (UTC) 104 | input: vec![], 105 | output: vec![], 106 | }, 107 | unknown: BTreeMap::new(), 108 | proprietary: BTreeMap::new(), 109 | xpub: BTreeMap::new(), 110 | version: 0, 111 | inputs: vec![], 112 | outputs: vec![], 113 | }; 114 | // figure out the outpoint from the txid 115 | let (outpoint, witness_utxo) = get_vout(cl, txid, btc(1.0)); 116 | let txin = TxIn { 117 | previous_output: outpoint, 118 | // set the sequence to a non-final number for the locktime transactions to be 119 | // processed correctly. 120 | // We waited 50 blocks, keep 49 for safety 121 | sequence: Sequence::from_height(49), 122 | ..Default::default() 123 | }; 124 | psbt.unsigned_tx.input.push(txin); 125 | // Get a new script pubkey from the node so that 126 | // the node wallet tracks the receiving transaction 127 | // and we can check it by gettransaction RPC. 128 | let addr = cl.new_address_with_type(AddressType::Bech32).unwrap(); 129 | psbt.unsigned_tx.output.push(TxOut { 130 | value: Amount::from_sat(99_999_000), 131 | script_pubkey: addr.script_pubkey(), 132 | }); 133 | let input = psbt::Input { 134 | witness_utxo: Some(witness_utxo), 135 | witness_script: Some(desc.explicit_script().unwrap()), 136 | ..Default::default() 137 | }; 138 | psbt.inputs.push(input); 139 | psbt.update_input_with_descriptor(0, desc).unwrap(); 140 | psbt.outputs.push(psbt::Output::default()); 141 | psbts.push(psbt); 142 | } 143 | 144 | let mut spend_txids = vec![]; 145 | // Sign the transactions with all keys 146 | // AKA the signer role of psbt 147 | for i in 0..psbts.len() { 148 | let wsh_derived = desc_vec[i].derived_descriptor(&secp).unwrap(); 149 | let ms = if let Descriptor::Wsh(wsh) = &wsh_derived { 150 | match wsh.as_inner() { 151 | miniscript::descriptor::WshInner::Ms(ms) => ms, 152 | _ => unreachable!(), 153 | } 154 | } else { 155 | unreachable!("Only Wsh descriptors are supported"); 156 | }; 157 | 158 | let sks_reqd: Vec<_> = ms 159 | .iter_pk() 160 | .map(|pk| sks[pks.iter().position(|&x| x == pk).unwrap()]) 161 | .collect(); 162 | // Get the required sighash message 163 | let amt = btc(1); 164 | let mut sighash_cache = bitcoin::sighash::SighashCache::new(&psbts[i].unsigned_tx); 165 | let sighash_type = bitcoin::sighash::EcdsaSighashType::All; 166 | let sighash = sighash_cache 167 | .p2wsh_signature_hash(0, &ms.encode(), amt, sighash_type) 168 | .unwrap(); 169 | 170 | // requires both signing and verification because we check the tx 171 | // after we psbt extract it 172 | let msg = secp256k1::Message::from_digest(sighash.to_byte_array()); 173 | 174 | // Finally construct the signature and add to psbt 175 | for sk in sks_reqd { 176 | let signature = secp.sign_ecdsa(&msg, &sk); 177 | let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; 178 | psbts[i].inputs[0] 179 | .partial_sigs 180 | .insert(pk, bitcoin::ecdsa::Signature { signature, sighash_type }); 181 | } 182 | // Add the hash preimages to the psbt 183 | psbts[i].inputs[0] 184 | .sha256_preimages 185 | .insert(testdata.pubdata.sha256, testdata.secretdata.sha256_pre.to_vec()); 186 | psbts[i].inputs[0].hash256_preimages.insert( 187 | sha256d::Hash::from_byte_array(testdata.pubdata.hash256.to_byte_array()), 188 | testdata.secretdata.hash256_pre.to_vec(), 189 | ); 190 | println!("{}", ms); 191 | psbts[i].inputs[0] 192 | .hash160_preimages 193 | .insert(testdata.pubdata.hash160, testdata.secretdata.hash160_pre.to_vec()); 194 | psbts[i].inputs[0] 195 | .ripemd160_preimages 196 | .insert(testdata.pubdata.ripemd160, testdata.secretdata.ripemd160_pre.to_vec()); 197 | // Finalize the transaction using psbt 198 | // Let miniscript do it's magic! 199 | if let Err(e) = psbts[i].finalize_mall_mut(&secp) { 200 | // All miniscripts should satisfy 201 | panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i); 202 | } else { 203 | let tx = psbts[i].extract(&secp).unwrap(); 204 | 205 | // Send the transactions to bitcoin node for mining. 206 | // Regtest mode has standardness checks 207 | // Check whether the node accepts the transactions 208 | let txid = cl 209 | .send_raw_transaction(&tx) 210 | .unwrap_or_else(|_| panic!("{} send tx failed for ms {}", i, ms)) 211 | .txid() 212 | .expect("conversion to model failed"); 213 | spend_txids.push(txid); 214 | } 215 | } 216 | // Finally mine the blocks and await confirmations 217 | let _blocks = cl 218 | .generate_to_address(10, &cl.new_address().unwrap()) 219 | .unwrap(); 220 | // Get the required transactions from the node mined in the blocks. 221 | for txid in spend_txids { 222 | // Check whether the transaction is mined in blocks 223 | // Assert that the confirmations are > 0. 224 | let num_conf = cl.get_transaction(txid).unwrap().confirmations; 225 | assert!(num_conf > 0); 226 | } 227 | } 228 | 229 | #[test] 230 | fn test_setup() { setup::setup(); } 231 | 232 | #[test] 233 | fn tests_from_cpp() { 234 | let cl = &setup::setup().client; 235 | let testdata = TestData::new_fixed_data(50); 236 | test_from_cpp_ms(cl, &testdata); 237 | } 238 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.63.0" 2 | # plan API returns Self as an error type for an large-ish enum 3 | large-error-threshold = 256 4 | -------------------------------------------------------------------------------- /contrib/crates.sh: -------------------------------------------------------------------------------- 1 | # No shebang, this file should not be executed. 2 | # shellcheck disable=SC2148 3 | # 4 | # disable verify unused vars, despite the fact that they are used when sourced 5 | # shellcheck disable=SC2034 6 | 7 | # Crates in this workspace to test. 8 | CRATES=("." "fuzz") 9 | -------------------------------------------------------------------------------- /contrib/integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Run the integration tests using the binary in `bitcoind-tests/bin`. 4 | 5 | set -euo pipefail 6 | 7 | REPO_DIR=$(git rev-parse --show-toplevel) 8 | 9 | # Make all cargo invocations verbose. 10 | export CARGO_TERM_VERBOSE=true 11 | 12 | BITCOIND_EXE="$REPO_DIR/bitcoind-tests/bin/bitcoind" cargo test --verbose 13 | -------------------------------------------------------------------------------- /contrib/pin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Do pinning as required for current MSRV. 4 | 5 | set -euo pipefail 6 | 7 | cargo update -p cc --precise 1.0.79 8 | -------------------------------------------------------------------------------- /contrib/test_vars.sh: -------------------------------------------------------------------------------- 1 | # No shebang, this file should not be executed. 2 | # shellcheck disable=SC2148 3 | # 4 | # disable verify unused vars, despite the fact that they are used when sourced 5 | # shellcheck disable=SC2034 6 | 7 | # Test all these features with "std" enabled. 8 | FEATURES_WITH_STD="compiler trace serde rand base64" 9 | 10 | # Test all these features without "std" enabled. 11 | FEATURES_WITHOUT_STD="compiler trace serde rand base64" 12 | 13 | # Run these examples. 14 | # Note `examples/big` should not be run. 15 | EXAMPLES="htlc:std,compiler parse:std sign_multisig:std verify_tx:std xpub_descriptors:std taproot:std,compiler psbt_sign_finalize:std,base64 taptree_of_horror:std,compiler" 16 | -------------------------------------------------------------------------------- /contrib/update-lock-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Update the minimal/recent lock file 4 | 5 | set -euo pipefail 6 | 7 | for file in Cargo-minimal.lock Cargo-recent.lock; do 8 | cp --force "$file" Cargo.lock 9 | cargo check 10 | cp --force Cargo.lock "$file" 11 | done 12 | -------------------------------------------------------------------------------- /contrib/whitelist_deps.sh: -------------------------------------------------------------------------------- 1 | # No shebang, this file should not be executed. 2 | # shellcheck disable=SC2148 3 | # 4 | # disable verify unused vars, despite the fact that they are used when sourced 5 | # shellcheck disable=SC2034 6 | 7 | # Remove once we upgrade to `bitcoin v0.32.0`. 8 | DUPLICATE_DEPS=("bech32") 9 | -------------------------------------------------------------------------------- /doc/ReasoningAboutMultipartyMiniscript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-bitcoin/rust-miniscript/1c4e95998bb325616ab0f1a3c47240b12c5a95df/doc/ReasoningAboutMultipartyMiniscript.pdf -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /doc/taproot_compiler.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-bitcoin/rust-miniscript/1c4e95998bb325616ab0f1a3c47240b12c5a95df/doc/taproot_compiler.pdf -------------------------------------------------------------------------------- /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 } 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /embedded/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 2048K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 512K 5 | } -------------------------------------------------------------------------------- /embedded/scripts/env.sh: -------------------------------------------------------------------------------- 1 | # we don't want shebangs in env.sh, disable shellcheck warning 2 | # shellcheck disable=SC2148 3 | export RUSTFLAGS="-C link-arg=-Tlink.x" 4 | export CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER="qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" -------------------------------------------------------------------------------- /embedded/scripts/install-deps: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | apt install gcc-arm-none-eabi qemu-system-arm gdb-multiarch -------------------------------------------------------------------------------- /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().to_wu(), 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/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 | 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 | type TargetPk = XOnlyPublicKey; 87 | type Error = (); 88 | 89 | fn pk(&mut self, pk: &String) -> Result { 90 | self.pk_map.get(pk).copied().ok_or(()) 91 | } 92 | 93 | // We don't need to implement these methods as we are not using them in the policy. 94 | // Fail if we encounter any hash fragments. See also translate_hash_clone! macro. 95 | translate_hash_fail!(String, XOnlyPublicKey, Self::Error); 96 | } 97 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 txin = TxIn { 85 | previous_output: outpoint, 86 | sequence: Sequence::from_height(26), 87 | ..Default::default() 88 | }; 89 | psbt.unsigned_tx.input.push(txin); 90 | 91 | psbt.unsigned_tx.output.push(TxOut { 92 | script_pubkey: receiver.script_pubkey(), 93 | value: Amount::from_sat(amount / 5 - 500), 94 | }); 95 | 96 | psbt.unsigned_tx.output.push(TxOut { 97 | script_pubkey: bridge_descriptor.script_pubkey(), 98 | value: Amount::from_sat(amount * 4 / 5), 99 | }); 100 | 101 | // Generating signatures & witness data 102 | 103 | let mut input = psbt::Input::default(); 104 | input 105 | .update_with_descriptor_unchecked(&bridge_descriptor) 106 | .unwrap(); 107 | 108 | input.witness_utxo = Some(witness_utxo.clone()); 109 | psbt.inputs.push(input); 110 | psbt.outputs.push(psbt::Output::default()); 111 | 112 | let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); 113 | 114 | let msg = psbt 115 | .sighash_msg(0, &mut sighash_cache, None) 116 | .unwrap() 117 | .to_secp_msg(); 118 | 119 | // Fixme: Take a parameter 120 | let hash_ty = bitcoin::sighash::EcdsaSighashType::All; 121 | 122 | let sk1 = backup1_private.inner; 123 | let sk2 = backup2_private.inner; 124 | 125 | // Finally construct the signature and add to psbt 126 | let sig1 = secp256k1.sign_ecdsa(&msg, &sk1); 127 | let pk1 = backup1_private.public_key(&secp256k1); 128 | assert!(secp256k1.verify_ecdsa(&msg, &sig1, &pk1.inner).is_ok()); 129 | 130 | // Second key just in case 131 | let sig2 = secp256k1.sign_ecdsa(&msg, &sk2); 132 | let pk2 = backup2_private.public_key(&secp256k1); 133 | assert!(secp256k1.verify_ecdsa(&msg, &sig2, &pk2.inner).is_ok()); 134 | 135 | psbt.inputs[0] 136 | .partial_sigs 137 | .insert(pk1, bitcoin::ecdsa::Signature { signature: sig1, sighash_type: hash_ty }); 138 | 139 | println!("{:#?}", psbt); 140 | println!("{}", psbt); 141 | 142 | psbt.finalize_mut(&secp256k1).unwrap(); 143 | println!("{:#?}", psbt); 144 | 145 | let tx = psbt.extract_tx().expect("failed to extract tx"); 146 | println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); 147 | } 148 | 149 | // Find the Outpoint by spk 150 | fn get_vout(tx: &Transaction, spk: &Script) -> (OutPoint, TxOut) { 151 | for (i, txout) in tx.clone().output.into_iter().enumerate() { 152 | if spk == &txout.script_pubkey { 153 | return (OutPoint::new(tx.compute_txid(), i as u32), txout); 154 | } 155 | } 156 | panic!("Only call get vout on functions which have the expected outpoint"); 157 | } 158 | -------------------------------------------------------------------------------- /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().to_wu(), 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 | #[rustfmt::skip] 97 | fn list_of_three_arbitrary_public_keys() -> Vec { 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 | signature: secp256k1::ecdsa::Signature::from_str( 122 | "3045\ 123 | 0221\ 124 | 00f7c3648c390d87578cd79c8016940aa8e3511c4104cb78daa8fb8e429375efc1\ 125 | 0220\ 126 | 531d75c136272f127a5dc14acc0722301cbddc222262934151f140da345af177", 127 | ) 128 | .unwrap(), 129 | sighash_type: 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, 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 | type TargetPk = XOnlyPublicKey; 22 | type Error = (); 23 | 24 | fn pk(&mut self, pk: &String) -> Result { 25 | self.pk_map.get(pk).copied().ok_or(()) 26 | } 27 | 28 | // We don't need to implement these methods as we are not using them in the policy. 29 | // Fail if we encounter any hash fragments. See also translate_hash_clone! macro. 30 | translate_hash_fail!(String, XOnlyPublicKey, Self::Error); 31 | } 32 | 33 | fn main() { 34 | let pol_str = "or( 35 | 99@thresh(2, 36 | pk(hA), pk(S) 37 | ),1@or( 38 | 99@pk(Ca), 39 | 1@and(pk(In), older(9)) 40 | ) 41 | )" 42 | .replace(&[' ', '\n', '\t'][..], ""); 43 | 44 | let _ms = Miniscript::::from_str("and_v(v:ripemd160(H),pk(A))").unwrap(); 45 | let pol = Concrete::::from_str(&pol_str).unwrap(); 46 | // In case we can't find an internal key for the given policy, we set the internal key to 47 | // a random pubkey as specified by BIP341 (which are *unspendable* by any party :p) 48 | let desc = pol.compile_tr(Some("UNSPENDABLE_KEY".to_string())).unwrap(); 49 | 50 | let expected_desc = 51 | Descriptor::::from_str("tr(Ca,{and_v(v:pk(In),older(9)),and_v(v:pk(hA),pk(S))})") 52 | .unwrap(); 53 | assert_eq!(desc, expected_desc); 54 | 55 | // Check whether the descriptors are safe. 56 | assert!(desc.sanity_check().is_ok()); 57 | 58 | // Descriptor type and version should match respectively for taproot 59 | let desc_type = desc.desc_type(); 60 | assert_eq!(desc_type, DescriptorType::Tr); 61 | assert_eq!(desc_type.segwit_version().unwrap(), WitnessVersion::V1); 62 | 63 | if let Descriptor::Tr(ref p) = desc { 64 | // Check if internal key is correctly inferred as Ca 65 | // assert_eq!(p.internal_key(), &pubkeys[2]); 66 | assert_eq!(p.internal_key(), "Ca"); 67 | 68 | // Iterate through scripts 69 | let mut iter = p.leaves(); 70 | let mut next = iter.next().unwrap(); 71 | assert_eq!( 72 | (next.depth(), next.miniscript().as_ref()), 73 | ( 74 | 1u8, 75 | &Miniscript::::from_str("and_v(vc:pk_k(In),older(9))").unwrap() 76 | ) 77 | ); 78 | next = iter.next().unwrap(); 79 | assert_eq!( 80 | (next.depth(), next.miniscript().as_ref()), 81 | (1u8, &Miniscript::::from_str("and_v(v:pk(hA),pk(S))").unwrap()) 82 | ); 83 | assert_eq!(iter.next(), None); 84 | } 85 | 86 | let mut pk_map = HashMap::new(); 87 | 88 | // We require secp for generating a random XOnlyPublicKey 89 | let secp = secp256k1::Secp256k1::new(); 90 | let key_pair = Keypair::new(&secp, &mut rand::thread_rng()); 91 | // Random unspendable XOnlyPublicKey provided for compilation to Taproot Descriptor 92 | let (unspendable_pubkey, _parity) = XOnlyPublicKey::from_keypair(&key_pair); 93 | 94 | pk_map.insert("UNSPENDABLE_KEY".to_string(), unspendable_pubkey); 95 | let pubkeys = hardcoded_xonlypubkeys(); 96 | pk_map.insert("hA".to_string(), pubkeys[0]); 97 | pk_map.insert("S".to_string(), pubkeys[1]); 98 | pk_map.insert("Ca".to_string(), pubkeys[2]); 99 | pk_map.insert("In".to_string(), pubkeys[3]); 100 | let mut t = StrPkTranslator { pk_map }; 101 | 102 | let real_desc = desc.translate_pk(&mut t).unwrap(); 103 | 104 | // Max satisfaction weight for compilation, corresponding to the script-path spend 105 | // `and_v(PUBKEY_1,PUBKEY_2) at tap tree depth 1, having: 106 | // 107 | // max_witness_size = varint(control_block_size) + control_block size + 108 | // varint(script_size) + script_size + max_satisfaction_size 109 | // = 1 + 65 + 1 + 68 + 132 = 269 110 | let max_sat_wt = real_desc.max_weight_to_satisfy().unwrap().to_wu(); 111 | assert_eq!(max_sat_wt, 267); 112 | 113 | // Compute the bitcoin address and check if it matches 114 | let network = Network::Bitcoin; 115 | let addr = real_desc.address(network).unwrap(); 116 | let expected_addr = bitcoin::Address::from_str( 117 | "bc1p4l2xzq7js40965s5w0fknd287kdlmt2dljte37zsc5a34u0h9c4q85snyd", 118 | ) 119 | .unwrap() 120 | .assume_checked(); 121 | assert_eq!(addr, expected_addr); 122 | } 123 | 124 | fn hardcoded_xonlypubkeys() -> Vec { 125 | let serialized_keys: [[u8; 32]; 4] = [ 126 | [ 127 | 22, 37, 41, 4, 57, 254, 191, 38, 14, 184, 200, 133, 111, 226, 145, 183, 245, 112, 100, 128 | 42, 69, 210, 146, 60, 179, 170, 174, 247, 231, 224, 221, 52, 129 | ], 130 | [ 131 | 194, 16, 47, 19, 231, 1, 0, 143, 203, 11, 35, 148, 101, 75, 200, 15, 14, 54, 222, 208, 132 | 31, 205, 191, 215, 80, 69, 214, 126, 10, 124, 107, 154, 133 | ], 134 | [ 135 | 202, 56, 167, 245, 51, 10, 193, 145, 213, 151, 66, 122, 208, 43, 10, 17, 17, 153, 170, 136 | 29, 89, 133, 223, 134, 220, 212, 166, 138, 2, 152, 122, 16, 137 | ], 138 | [ 139 | 50, 23, 194, 4, 213, 55, 42, 210, 67, 101, 23, 3, 195, 228, 31, 70, 127, 79, 21, 188, 140 | 168, 39, 134, 58, 19, 181, 3, 63, 235, 103, 155, 213, 141 | ], 142 | ]; 143 | let mut keys: Vec = vec![]; 144 | for key in serialized_keys { 145 | keys.push(XOnlyPublicKey::from_slice(&key).unwrap()); 146 | } 147 | keys 148 | } 149 | -------------------------------------------------------------------------------- /examples/taptree_of_horror/README.md: -------------------------------------------------------------------------------- 1 | # Taptree of Horror Example 2 | 3 | ### Running this example: 4 | - `cargo run --example taptree_of_horror --features "compiler"` 5 | 6 | ### Originally based on the TABConf 6, CTB. 7 | The challenge can be found here: 8 | - https://tabctb.com/six 9 | - https://tabctb.com/six/thebeginning/thetree/grim/iacceptyourterms.html 10 | 11 | ### This example demonstrates: 12 | - Providing multiple extended private key (xpriv) descriptors for sample personas. 13 | - Creating a policy using logical 'and/or' conditions with preimages and signatures and timelocks. 14 | - Structuring a Taproot tree (taptree) with an internal key into logical branches and leaves based on the policy. 15 | - Implementing nine complex tapleaves within the taptree. 16 | - Building a spending transaction that signs and satisfies one of the tapleaves using signatures, preimages and a timelock. 17 | 18 | ### Helpful Graphic to visualize using Excalidraw 19 | ![taptree_of_horror](./taptree_of_horror.png) 20 | 21 | -------------------------------------------------------------------------------- /examples/taptree_of_horror/helper_fns.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use bitcoin::bip32::{DerivationPath, Xpriv}; 4 | use bitcoin::hashes::{ripemd160, sha256, Hash}; 5 | use miniscript::descriptor::DescriptorSecretKey; 6 | use miniscript::ToPublicKey; 7 | use secp256k1::Secp256k1; 8 | 9 | use crate::KEYS_PER_PERSONA; 10 | 11 | pub fn produce_grim_hash(secret: &str) -> (sha256::Hash, ripemd160::Hash) { 12 | let mut hash_holder = sha256::Hash::hash(secret.as_bytes()); 13 | for _i in 0..5 { 14 | hash_holder = sha256::Hash::hash(hash_holder.as_byte_array()); 15 | //println!("{} hash: {}", i, hash_holder); 16 | } 17 | 18 | let ripemd_160_final = ripemd160::Hash::hash(hash_holder.as_byte_array()); 19 | (hash_holder, ripemd_160_final) 20 | } 21 | 22 | pub fn produce_kelly_hash(secret: &str) -> (sha256::Hash, sha256::Hash) { 23 | let prepreimage = secret.as_bytes(); 24 | let preimage_256_hash = sha256::Hash::hash(prepreimage); 25 | let result256_final = sha256::Hash::hash(&preimage_256_hash.to_byte_array()); 26 | (preimage_256_hash, result256_final) 27 | } 28 | 29 | pub fn produce_key_pairs( 30 | desc: DescriptorSecretKey, 31 | secp: &Secp256k1, 32 | derivation_without_index: &str, 33 | _alias: &str, 34 | ) -> (Vec, Vec) { 35 | let mut pks = Vec::new(); 36 | let mut prvs = Vec::new(); 37 | 38 | let xprv = match &desc { 39 | DescriptorSecretKey::XPrv(xpriv) => xpriv, 40 | _ => panic!("not an xpriv"), 41 | }; 42 | 43 | for i in 0..KEYS_PER_PERSONA { 44 | let pk = desc 45 | .to_public(secp) 46 | .unwrap() 47 | .at_derivation_index(i.try_into().unwrap()) 48 | .unwrap() 49 | .to_public_key(); 50 | 51 | let derivation_with_index = format!("{}/{}", derivation_without_index, i); 52 | let derivation_path = DerivationPath::from_str(&derivation_with_index).unwrap(); 53 | let derived_xpriv: Xpriv = xprv.xkey.derive_priv(secp, &derivation_path).unwrap(); 54 | 55 | pks.push(pk); 56 | prvs.push(derived_xpriv); 57 | } 58 | (pks, prvs) 59 | } 60 | -------------------------------------------------------------------------------- /examples/taptree_of_horror/taptree_of_horror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-bitcoin/rust-miniscript/1c4e95998bb325616ab0f1a3c47240b12c5a95df/examples/taptree_of_horror/taptree_of_horror.png -------------------------------------------------------------------------------- /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.sighash_type == bitcoin::sighash::EcdsaSighashType::All 91 | && secp 92 | .verify_ecdsa(&message, &ecdsa_sig.signature, &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 | #[rustfmt::skip] 106 | fn hard_coded_transaction() -> bitcoin::Transaction { 107 | // tx `f27eba163c38ad3f34971198687a3f1882b7ec818599ffe469a8440d82261c98` 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "descriptor-fuzz" 3 | edition = "2021" 4 | rust-version = "1.63.0" 5 | version = "0.0.1" 6 | authors = ["Generated by fuzz/generate-files.sh"] 7 | publish = false 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | honggfuzz = { version = "0.5.56", default-features = false } 14 | # We shouldn't need an explicit version on the next line, but Andrew's tools 15 | # choke on it otherwise. See https://github.com/nix-community/crate2nix/issues/373 16 | miniscript = { path = "..", features = [ "compiler" ], version = "13.0" } 17 | old_miniscript = { package = "miniscript", version = "12.3" } 18 | 19 | regex = "1.0" 20 | 21 | [[bin]] 22 | name = "compile_descriptor" 23 | path = "fuzz_targets/compile_descriptor.rs" 24 | 25 | [[bin]] 26 | name = "compile_taproot" 27 | path = "fuzz_targets/compile_taproot.rs" 28 | 29 | [[bin]] 30 | name = "miniscript_satisfy" 31 | path = "fuzz_targets/miniscript_satisfy.rs" 32 | 33 | [[bin]] 34 | name = "parse_descriptor" 35 | path = "fuzz_targets/parse_descriptor.rs" 36 | 37 | [[bin]] 38 | name = "parse_descriptor_priv" 39 | path = "fuzz_targets/parse_descriptor_priv.rs" 40 | 41 | [[bin]] 42 | name = "parse_descriptor_secret" 43 | path = "fuzz_targets/parse_descriptor_secret.rs" 44 | 45 | [[bin]] 46 | name = "regression_descriptor_parse" 47 | path = "fuzz_targets/regression_descriptor_parse.rs" 48 | 49 | [[bin]] 50 | name = "regression_taptree" 51 | path = "fuzz_targets/regression_taptree.rs" 52 | 53 | [[bin]] 54 | name = "roundtrip_concrete" 55 | path = "fuzz_targets/roundtrip_concrete.rs" 56 | 57 | [[bin]] 58 | name = "roundtrip_descriptor" 59 | path = "fuzz_targets/roundtrip_descriptor.rs" 60 | 61 | [[bin]] 62 | name = "roundtrip_miniscript_script" 63 | path = "fuzz_targets/roundtrip_miniscript_script.rs" 64 | 65 | [[bin]] 66 | name = "roundtrip_miniscript_script_tap" 67 | path = "fuzz_targets/roundtrip_miniscript_script_tap.rs" 68 | 69 | [[bin]] 70 | name = "roundtrip_miniscript_str" 71 | path = "fuzz_targets/roundtrip_miniscript_str.rs" 72 | 73 | [[bin]] 74 | name = "roundtrip_semantic" 75 | path = "fuzz_targets/roundtrip_semantic.rs" 76 | -------------------------------------------------------------------------------- /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, and disable some hardening flags 17 | which conflict with the way honggfuzz builds its targets, by running 18 | 19 | nix-shell -p libopcodes_2_38 -p libunwind 20 | # In the nix-shell run these 21 | NIX_HARDENING_ENABLE=''${NIX_HARDENING_ENABLE/fortify/} 22 | NIX_HARDENING_ENABLE=''${NIX_HARDENING_ENABLE/fortify3/} 23 | 24 | and then run fuzz.sh as above. 25 | 26 | # Fuzzing with weak cryptography 27 | 28 | You may wish to replace the hashing and signing code with broken crypto, 29 | which will be faster and enable the fuzzer to do otherwise impossible 30 | things such as forging signatures or finding preimages to hashes. 31 | 32 | Doing so may result in spurious bug reports since the broken crypto does 33 | not respect the encoding or algebraic invariants upheld by the real crypto. We 34 | would like to improve this but it's a nontrivial problem -- though not 35 | beyond the abilities of a motivated student with a few months of time. 36 | Please let us know if you are interested in taking this on! 37 | 38 | Meanwhile, to use the broken crypto, simply compile (and run the fuzzing 39 | scripts) with 40 | 41 | RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz" 42 | 43 | which will replace the hashing library with broken hashes, and the 44 | secp256k1 library with broken cryptography. 45 | 46 | Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a 47 | fuzzer can break your crypto, so can anybody. 48 | 49 | # Long-term fuzzing 50 | 51 | To see the full list of targets, the most straightforward way is to run 52 | 53 | source ./fuzz-util.sh 54 | listTargetNames 55 | 56 | To run each of them for an hour, run 57 | 58 | ./cycle.sh 59 | 60 | To run a single fuzztest indefinitely, run 61 | 62 | cargo hfuzz run 63 | 64 | `cycle.sh` uses the `chrt` utility to try to reduce the priority of the 65 | jobs. If you would like to run for longer, the most straightforward way 66 | is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, 67 | you will need to implement a custom harness. 68 | 69 | # Adding fuzz tests 70 | 71 | All fuzz tests can be found in the `fuzz_target/` directory. Adding a new 72 | one is as simple as copying an existing one and editing the `do_test` 73 | function to do what you want. 74 | 75 | If your test clearly belongs to a specific crate, please put it in that 76 | crate's directory. Otherwise you can put it directly in `fuzz_target/`. 77 | 78 | If you need to add dependencies, edit the file `generate-files.sh` to add 79 | it to the generated `Cargo.toml`. 80 | 81 | Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by 82 | running 83 | 84 | ./generate-files.sh 85 | 86 | Then to test your fuzztest, run 87 | 88 | ./fuzz.sh 89 | 90 | If it is working, you will see a rapid stream of data for many seconds 91 | (you can hit Ctrl+C to stop it early). If not, you should quickly see 92 | an error. 93 | 94 | # Reproducing Failures 95 | 96 | If a fuzztest fails, it will exit with a summary which looks something like 97 | 98 | ``` 99 | ... 100 | fuzzTarget : hfuzz_target/x86_64-unknown-linux-gnu/release/hashes_sha256 101 | CRASH: 102 | DESCRIPTION: 103 | ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov 104 | FUZZ_FNAME: hfuzz_workspace/hashes_sha256/SIGABRT.PC.7ffff7c8abc7.STACK.18826d9b64.CODE.-6.ADDR.0.INSTR.mov____%eax,%ebp.fuzz 105 | ... 106 | ===================================================================== 107 | fff400610004 108 | ``` 109 | 110 | The final line is a hex-encoded version of the input that caused the crash. You 111 | can test this directly by editing the `duplicate_crash` test to copy/paste the 112 | hex output into the call to `extend_vec_from_hex`. Then run the test with 113 | 114 | cargo test 115 | 116 | Note that if you set your `RUSTFLAGS` while fuzzing (see above) you must make 117 | sure they are set the same way when running `cargo test`. 118 | 119 | If the `duplicate_crash` function is not present, please add it. A template is 120 | as follows: 121 | 122 | ``` 123 | #[cfg(test)] 124 | mod tests { 125 | use miniscript::bitcoin::hex::FromHex; 126 | 127 | #[test] 128 | fn duplicate_crash() { 129 | let v = Vec::from_hex("abcd").unwrap(); 130 | super::do_test(&v); 131 | } 132 | } 133 | ``` 134 | -------------------------------------------------------------------------------- /fuzz/cycle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Continuously 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 | # can't find the file because of the ENV var 11 | # shellcheck source=/dev/null 12 | source "$REPO_DIR/fuzz/fuzz-util.sh" 13 | 14 | while : 15 | do 16 | for targetFile in $(listTargetFiles); do 17 | targetName=$(targetFileToName "$targetFile") 18 | echo "Fuzzing target $targetName ($targetFile)" 19 | 20 | # fuzz for one hour 21 | HFUZZ_RUN_ARGS='--run_time 3600' chrt -i 0 cargo hfuzz run "$targetName" 22 | # minimize the corpus 23 | HFUZZ_RUN_ARGS="-i hfuzz_workspace/$targetName/input/ -P -M" chrt -i 0 cargo hfuzz run "$targetName" 24 | done 25 | done 26 | 27 | -------------------------------------------------------------------------------- /fuzz/fuzz-util.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | REPO_DIR=$(git rev-parse --show-toplevel) 4 | 5 | # Sort order is effected by locale. See `man sort`. 6 | # > Set LC_ALL=C to get the traditional sort order that uses native byte values. 7 | export LC_ALL=C 8 | 9 | listTargetFiles() { 10 | pushd "$REPO_DIR/fuzz" > /dev/null || exit 1 11 | find fuzz_targets/ -type f -name "*.rs" | sort 12 | popd > /dev/null || exit 1 13 | } 14 | 15 | targetFileToName() { 16 | echo "$1" \ 17 | | sed 's/^fuzz_targets\///' \ 18 | | sed 's/\.rs$//' \ 19 | | sed 's/\//_/g' 20 | } 21 | 22 | targetFileToHFuzzInputArg() { 23 | baseName=$(basename "$1") 24 | dirName="${baseName%.*}" 25 | if [ -d "hfuzz_input/$dirName" ]; then 26 | echo "HFUZZ_INPUT_ARGS=\"-f hfuzz_input/$FILE/input\"" 27 | fi 28 | } 29 | 30 | listTargetNames() { 31 | for target in $(listTargetFiles); do 32 | targetFileToName "$target" 33 | done 34 | } 35 | 36 | # Utility function to avoid CI failures on Windows 37 | checkWindowsFiles() { 38 | incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l) 39 | if [ "$incorrectFilenames" -gt 0 ]; then 40 | echo "Bailing early because there is a Windows-incompatible filename in the tree." 41 | exit 2 42 | fi 43 | } 44 | 45 | # Checks whether a fuzz case output some report, and dumps it in hex 46 | checkReport() { 47 | reportFile="hfuzz_workspace/$1/HONGGFUZZ.REPORT.TXT" 48 | if [ -f "$reportFile" ]; then 49 | cat "$reportFile" 50 | for CASE in "hfuzz_workspace/$1/SIG"*; do 51 | xxd -p -c10000 < "$CASE" 52 | done 53 | exit 1 54 | fi 55 | } 56 | -------------------------------------------------------------------------------- /fuzz/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | REPO_DIR=$(git rev-parse --show-toplevel) 5 | 6 | # can't find the file because of the ENV var 7 | # shellcheck source=/dev/null 8 | source "$REPO_DIR/fuzz/fuzz-util.sh" 9 | 10 | # Check that input files are correct Windows file names 11 | checkWindowsFiles 12 | 13 | if [ "$1" == "" ]; then 14 | targetFiles="$(listTargetFiles)" 15 | else 16 | targetFiles=fuzz_targets/"$1".rs 17 | fi 18 | 19 | cargo --version 20 | rustc --version 21 | 22 | # Testing 23 | cargo install --force honggfuzz --no-default-features 24 | for targetFile in $targetFiles; do 25 | targetName=$(targetFileToName "$targetFile") 26 | echo "Fuzzing target $targetName ($targetFile)" 27 | if [ -d "hfuzz_input/$targetName" ]; then 28 | HFUZZ_INPUT_ARGS="-f hfuzz_input/$targetName/input\"" 29 | else 30 | HFUZZ_INPUT_ARGS="" 31 | fi 32 | HFUZZ_RUN_ARGS="--run_time 30 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run "$targetName" 33 | 34 | checkReport "$targetName" 35 | done 36 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/compile_descriptor.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::{policy, Miniscript, Segwitv0}; 7 | use policy::Liftable; 8 | 9 | type Script = Miniscript; 10 | type Policy = policy::Concrete; 11 | 12 | fn do_test(data: &[u8]) { 13 | let data_str = String::from_utf8_lossy(data); 14 | if let Ok(pol) = Policy::from_str(&data_str) { 15 | // Compile 16 | if let Ok(desc) = pol.compile::() { 17 | // Lift 18 | assert_eq!(desc.lift().unwrap().sorted(), pol.lift().unwrap().sorted()); 19 | // Try to roundtrip the output of the compiler 20 | let output = desc.to_string(); 21 | if let Ok(desc) = Script::from_str(&output) { 22 | let rtt = desc.to_string(); 23 | assert_eq!(output.to_lowercase(), rtt.to_lowercase()); 24 | } else { 25 | panic!("compiler output something unparseable: {}", output) 26 | } 27 | } 28 | } 29 | } 30 | 31 | fn main() { 32 | loop { 33 | fuzz!(|data| { 34 | do_test(data); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/compile_taproot.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::{policy, Miniscript, Tap}; 7 | use policy::Liftable; 8 | 9 | type Script = Miniscript; 10 | type Policy = policy::Concrete; 11 | 12 | fn do_test(data: &[u8]) { 13 | let data_str = String::from_utf8_lossy(data); 14 | if let Ok(pol) = Policy::from_str(&data_str) { 15 | // Compile 16 | if let Ok(desc) = pol.compile::() { 17 | // Lift 18 | assert_eq!(desc.lift().unwrap().sorted(), pol.lift().unwrap().sorted()); 19 | // Try to roundtrip the output of the compiler 20 | let output = desc.to_string(); 21 | if let Ok(desc) = Script::from_str(&output) { 22 | let rtt = desc.to_string(); 23 | assert_eq!(output.to_lowercase(), rtt.to_lowercase()); 24 | } else { 25 | panic!("compiler output something unparseable: {}", output) 26 | } 27 | } 28 | } 29 | } 30 | 31 | fn main() { 32 | loop { 33 | fuzz!(|data| { 34 | do_test(data); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/miniscript_satisfy.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use descriptor_fuzz::FuzzPk; 6 | use honggfuzz::fuzz; 7 | use miniscript::bitcoin::hashes::hash160; 8 | use miniscript::bitcoin::locktime::{absolute, relative}; 9 | use miniscript::bitcoin::taproot::Signature; 10 | use miniscript::bitcoin::{secp256k1, PublicKey, TapLeafHash, TapSighashType, XOnlyPublicKey}; 11 | use miniscript::{Miniscript, Satisfier, Segwitv0, Tap}; 12 | 13 | struct FuzzSatisfier<'b> { 14 | idx: AtomicUsize, 15 | buf: &'b [u8], 16 | } 17 | 18 | impl FuzzSatisfier<'_> { 19 | fn read_byte(&self) -> Option { 20 | let idx = self.idx.fetch_add(1, Ordering::SeqCst); 21 | self.buf.get(idx).copied() 22 | } 23 | } 24 | 25 | impl Satisfier for FuzzSatisfier<'_> { 26 | fn lookup_tap_key_spend_sig(&self) -> Option { 27 | let b = self.read_byte()?; 28 | if b & 1 == 1 { 29 | // FIXME in later version of rust-secp we can use from_byte_array 30 | let secp_sig = secp256k1::schnorr::Signature::from_slice(&[0xab; 64]).unwrap(); 31 | Some(Signature { signature: secp_sig, sighash_type: TapSighashType::Default }) 32 | } else { 33 | None 34 | } 35 | } 36 | 37 | fn lookup_tap_leaf_script_sig(&self, _: &FuzzPk, _: &TapLeafHash) -> Option { 38 | self.lookup_tap_key_spend_sig() 39 | } 40 | 41 | // todo 42 | //fn lookup_tap_control_block_map( 43 | // &self, 44 | //) -> Option<&BTreeMap>; 45 | 46 | #[rustfmt::skip] 47 | fn lookup_raw_pkh_pk(&self, _: &hash160::Hash) -> Option { 48 | let b = self.read_byte()?; 49 | if b & 1 == 1 { 50 | // Decoding an uncompresssed key is extremely fast, while decoding 51 | // a compressed one is pretty slow. 52 | let secp_pk = secp256k1::PublicKey::from_slice(&[ 53 | 0x04, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x3b, 0x78, 0xce, 0x56, 0x3f, 56 | 0x89, 0xa0, 0xed, 0x94, 0x14, 0xf5, 0xaa, 0x28, 57 | 0xad, 0x0d, 0x96, 0xd6, 0x79, 0x5f, 0x9c, 0x63, 58 | 59 | 0x3f, 0x39, 0x79, 0xbf, 0x72, 0xae, 0x82, 0x02, 60 | 0x98, 0x3d, 0xc9, 0x89, 0xae, 0xc7, 0xf2, 0xff, 61 | 0x2e, 0xd9, 0x1b, 0xdd, 0x69, 0xce, 0x02, 0xfc, 62 | 0x07, 0x00, 0xca, 0x10, 0x0e, 0x59, 0xdd, 0xf3, 63 | ]).unwrap(); 64 | 65 | if b & 2 == 2 { 66 | Some(PublicKey::new(secp_pk)) 67 | } else{ 68 | Some(PublicKey::new_uncompressed(secp_pk)) 69 | } 70 | } else { 71 | None 72 | } 73 | } 74 | 75 | fn lookup_raw_pkh_x_only_pk(&self, h: &hash160::Hash) -> Option { 76 | self.lookup_raw_pkh_pk(h) 77 | .map(|pk| pk.inner.x_only_public_key().0) 78 | } 79 | 80 | // todo 81 | //fn lookup_raw_pkh_ecdsa_sig(&self, h: &hash160::Hash) -> Option<(PublicKey, ecdsa::Signature)>; 82 | 83 | fn lookup_raw_pkh_tap_leaf_script_sig( 84 | &self, 85 | (h, _): &(hash160::Hash, TapLeafHash), 86 | ) -> Option<(XOnlyPublicKey, Signature)> { 87 | self.lookup_raw_pkh_x_only_pk(h) 88 | .zip(self.lookup_tap_key_spend_sig()) 89 | } 90 | 91 | fn lookup_sha256(&self, b: &u8) -> Option<[u8; 32]> { 92 | if *b & 1 == 1 { 93 | Some([*b; 32]) 94 | } else { 95 | None 96 | } 97 | } 98 | 99 | fn lookup_hash256(&self, b: &u8) -> Option<[u8; 32]> { 100 | if *b & 1 == 1 { 101 | Some([*b; 32]) 102 | } else { 103 | None 104 | } 105 | } 106 | 107 | fn lookup_ripemd160(&self, b: &u8) -> Option<[u8; 32]> { 108 | if *b & 1 == 1 { 109 | Some([*b; 32]) 110 | } else { 111 | None 112 | } 113 | } 114 | 115 | fn lookup_hash160(&self, b: &u8) -> Option<[u8; 32]> { 116 | if *b & 1 == 1 { 117 | Some([*b; 32]) 118 | } else { 119 | None 120 | } 121 | } 122 | 123 | fn check_older(&self, t: relative::LockTime) -> bool { t.to_consensus_u32() & 1 == 1 } 124 | 125 | fn check_after(&self, t: absolute::LockTime) -> bool { t.to_consensus_u32() & 1 == 1 } 126 | } 127 | 128 | fn do_test(data: &[u8]) { 129 | if data.len() < 3 { 130 | return; 131 | } 132 | let control = data[0]; 133 | let len = (usize::from(data[1]) << 8) + usize::from(data[2]); 134 | if data.len() < 3 + len { 135 | return; 136 | } 137 | 138 | let s = &data[3..3 + len]; 139 | let fuzz_sat = FuzzSatisfier { idx: AtomicUsize::new(0), buf: &data[3 + len..] }; 140 | 141 | let s = String::from_utf8_lossy(s); 142 | if control & 1 == 1 { 143 | let ms = match s.parse::>() { 144 | Ok(d) => d, 145 | Err(_) => return, 146 | }; 147 | 148 | let _ = ms.build_template(&fuzz_sat); 149 | } else { 150 | let ms = match s.parse::>() { 151 | Ok(d) => d, 152 | Err(_) => return, 153 | }; 154 | 155 | let _ = ms.build_template(&fuzz_sat); 156 | }; 157 | } 158 | 159 | fn main() { 160 | loop { 161 | fuzz!(|data| { 162 | do_test(data); 163 | }); 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 170 | let mut b = 0; 171 | for (idx, c) in hex.as_bytes().iter().enumerate() { 172 | b <<= 4; 173 | match *c { 174 | b'A'..=b'F' => b |= c - b'A' + 10, 175 | b'a'..=b'f' => b |= c - b'a' + 10, 176 | b'0'..=b'9' => b |= c - b'0', 177 | _ => panic!("Bad hex"), 178 | } 179 | if (idx & 1) == 1 { 180 | out.push(b); 181 | b = 0; 182 | } 183 | } 184 | } 185 | 186 | #[test] 187 | fn duplicate_crash() { 188 | let mut a = Vec::new(); 189 | extend_vec_from_hex("", &mut a); 190 | super::do_test(&a); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::DescriptorPublicKey; 7 | 8 | fn do_test(data: &[u8]) { 9 | let data_str = String::from_utf8_lossy(data); 10 | if let Ok(dpk) = DescriptorPublicKey::from_str(&data_str) { 11 | let _output = dpk.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 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor_priv.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::bitcoin::secp256k1; 5 | use miniscript::Descriptor; 6 | 7 | fn do_test(data: &[u8]) { 8 | let data_str = String::from_utf8_lossy(data); 9 | let secp = &secp256k1::Secp256k1::signing_only(); 10 | 11 | if let Ok((desc, _)) = Descriptor::parse_descriptor(secp, &data_str) { 12 | let _output = desc.to_string(); 13 | let _sanity_check = desc.sanity_check(); 14 | } 15 | } 16 | 17 | fn main() { 18 | loop { 19 | fuzz!(|data| { 20 | do_test(data); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor_secret.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::descriptor::DescriptorSecretKey; 7 | 8 | fn do_test(data: &[u8]) { 9 | let data_str = String::from_utf8_lossy(data); 10 | if let Ok(dsk) = DescriptorSecretKey::from_str(&data_str) { 11 | let output = dsk.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 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/regression_descriptor_parse.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::Descriptor; 5 | use old_miniscript::Descriptor as OldDescriptor; 6 | 7 | type Desc = Descriptor; 8 | type OldDesc = OldDescriptor; 9 | 10 | fn do_test(data: &[u8]) { 11 | let data_str = String::from_utf8_lossy(data); 12 | match (Desc::from_str(&data_str), OldDesc::from_str(&data_str)) { 13 | (Err(_), Err(_)) => {} 14 | (Ok(x), Err(e)) => panic!("new logic parses {} as {:?}, old fails with {}", data_str, x, e), 15 | (Err(e), Ok(x)) => panic!("old logic parses {} as {:?}, new fails with {}", data_str, x, e), 16 | (Ok(new), Ok(old)) => { 17 | use miniscript::policy::Liftable as _; 18 | use old_miniscript::policy::Liftable as _; 19 | 20 | assert_eq!( 21 | old.to_string(), 22 | new.to_string(), 23 | "input {} (left is old, right is new)", 24 | data_str 25 | ); 26 | 27 | match (new.lift(), old.lift()) { 28 | (Err(_), Err(_)) => {} 29 | (Ok(x), Err(e)) => { 30 | panic!("new logic lifts {} as {:?}, old fails with {}", data_str, x, e) 31 | } 32 | (Err(e), Ok(x)) => { 33 | panic!("old logic lifts {} as {:?}, new fails with {}", data_str, x, e) 34 | } 35 | (Ok(new), Ok(old)) => { 36 | assert_eq!( 37 | old.to_string(), 38 | new.to_string(), 39 | "lifted input {} (left is old, right is new)", 40 | data_str 41 | ) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | fn main() { 49 | loop { 50 | fuzz!(|data| { 51 | do_test(data); 52 | }); 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | #[test] 59 | fn duplicate_crash() { crate::do_test(b"tr(d,{0,{0,0}})") } 60 | } 61 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/regression_taptree.rs: -------------------------------------------------------------------------------- 1 | use descriptor_fuzz::FuzzPk; 2 | use honggfuzz::fuzz; 3 | use miniscript::descriptor::Tr; 4 | use old_miniscript::descriptor::Tr as OldTr; 5 | 6 | fn do_test(data: &[u8]) { 7 | let data_str = String::from_utf8_lossy(data); 8 | match (data_str.parse::>(), data_str.parse::>()) { 9 | (Err(_), Err(_)) => {} 10 | (Ok(_), Err(_)) => {} // 12.x logic rejects some parses for sanity reasons 11 | (Err(e), Ok(x)) => panic!("old logic parses {} as {:?}, new fails with {}", data_str, x, e), 12 | (Ok(new), Ok(old)) => { 13 | let new_si = new.spend_info(); 14 | let old_si = old.spend_info(); 15 | assert_eq!( 16 | old_si.internal_key(), 17 | new_si.internal_key(), 18 | "merkle root mismatch (left is old, new is right)", 19 | ); 20 | assert_eq!( 21 | old_si.merkle_root(), 22 | new_si.merkle_root(), 23 | "merkle root mismatch (left is old, new is right)", 24 | ); 25 | assert_eq!( 26 | old_si.output_key(), 27 | new_si.output_key(), 28 | "merkle root mismatch (left is old, new is right)", 29 | ); 30 | } 31 | } 32 | } 33 | 34 | fn main() { 35 | loop { 36 | fuzz!(|data| { 37 | do_test(data); 38 | }); 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | #[test] 45 | fn duplicate_crash() { crate::do_test(b"tr(0,{0,0})"); } 46 | } 47 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_concrete.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::policy; 7 | use regex::Regex; 8 | 9 | type Policy = policy::Concrete; 10 | 11 | fn do_test(data: &[u8]) { 12 | let data_str = String::from_utf8_lossy(data); 13 | if let Ok(pol) = Policy::from_str(&data_str) { 14 | let output = pol.to_string(); 15 | //remove all instances of 1@ 16 | let re = Regex::new("(\\D)1@").unwrap(); 17 | let output = re.replace_all(&output, "$1"); 18 | let data_str = re.replace_all(&data_str, "$1"); 19 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 20 | } 21 | } 22 | 23 | fn main() { 24 | loop { 25 | fuzz!(|data| { 26 | do_test(data); 27 | }); 28 | } 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 34 | let mut b = 0; 35 | for (idx, c) in hex.as_bytes().iter().enumerate() { 36 | b <<= 4; 37 | match *c { 38 | b'A'..=b'F' => b |= c - b'A' + 10, 39 | b'a'..=b'f' => b |= c - b'a' + 10, 40 | b'0'..=b'9' => b |= c - b'0', 41 | _ => panic!("Bad hex"), 42 | } 43 | if (idx & 1) == 1 { 44 | out.push(b); 45 | b = 0; 46 | } 47 | } 48 | } 49 | 50 | #[test] 51 | fn duplicate_crash() { 52 | let mut a = Vec::new(); 53 | extend_vec_from_hex("048531e80700ae6400670000af5168", &mut a); 54 | super::do_test(&a); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_descriptor.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::Descriptor; 7 | 8 | fn do_test(data: &[u8]) { 9 | let s = String::from_utf8_lossy(data); 10 | if let Ok(desc) = Descriptor::::from_str(&s) { 11 | let str2 = desc.to_string(); 12 | let desc2 = Descriptor::::from_str(&str2).unwrap(); 13 | 14 | assert_eq!(desc, desc2); 15 | } 16 | } 17 | 18 | fn main() { 19 | loop { 20 | fuzz!(|data| { 21 | do_test(data); 22 | }); 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 29 | let mut b = 0; 30 | for (idx, c) in hex.as_bytes().iter().enumerate() { 31 | b <<= 4; 32 | match *c { 33 | b'A'..=b'F' => b |= c - b'A' + 10, 34 | b'a'..=b'f' => b |= c - b'a' + 10, 35 | b'0'..=b'9' => b |= c - b'0', 36 | _ => panic!("Bad hex"), 37 | } 38 | if (idx & 1) == 1 { 39 | out.push(b); 40 | b = 0; 41 | } 42 | } 43 | } 44 | 45 | #[test] 46 | fn duplicate_crash3() { 47 | let mut a = Vec::new(); 48 | extend_vec_from_hex("747228726970656d616e645f6e5b5c79647228726970656d616e645f6e5b5c7964646464646464646464646464646464646464646464646464646b5f6872702c29", &mut a); 49 | super::do_test(&a); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_script.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::bitcoin::blockdata::script; 5 | use miniscript::{Miniscript, Segwitv0}; 6 | 7 | fn do_test(data: &[u8]) { 8 | // Try round-tripping as a script 9 | let script = script::Script::from_bytes(data); 10 | 11 | if let Ok(pt) = Miniscript::::parse(script) { 12 | let output = pt.encode(); 13 | assert_eq!(pt.script_size(), output.len()); 14 | assert_eq!(&output, script); 15 | } 16 | } 17 | 18 | fn main() { 19 | loop { 20 | fuzz!(|data| { 21 | do_test(data); 22 | }); 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 29 | let mut b = 0; 30 | for (idx, c) in hex.as_bytes().iter().enumerate() { 31 | b <<= 4; 32 | match *c { 33 | b'A'..=b'F' => b |= c - b'A' + 10, 34 | b'a'..=b'f' => b |= c - b'a' + 10, 35 | b'0'..=b'9' => b |= c - b'0', 36 | _ => panic!("Bad hex"), 37 | } 38 | if (idx & 1) == 1 { 39 | out.push(b); 40 | b = 0; 41 | } 42 | } 43 | } 44 | 45 | #[test] 46 | fn duplicate_crash3() { 47 | let mut a = Vec::new(); 48 | extend_vec_from_hex("1479002d00000020323731363342740000004000000000000000000000000000000000000063630004636363639c00000000000000000000", &mut a); 49 | super::do_test(&a); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_script_tap.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::bitcoin::blockdata::script; 5 | use miniscript::{Miniscript, Tap}; 6 | 7 | fn do_test(data: &[u8]) { 8 | // Try round-tripping as a script 9 | let script = script::Script::from_bytes(data); 10 | 11 | if let Ok(pt) = Miniscript::::parse(script) { 12 | let output = pt.encode(); 13 | assert_eq!(pt.script_size(), output.len()); 14 | assert_eq!(&output, script); 15 | } 16 | } 17 | 18 | fn main() { 19 | loop { 20 | fuzz!(|data| { 21 | do_test(data); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_str.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::{Miniscript, Segwitv0, Tap}; 7 | 8 | fn do_test(data: &[u8]) { 9 | let s = String::from_utf8_lossy(data); 10 | if let Ok(desc) = Miniscript::::from_str(&s) { 11 | let str2 = desc.to_string(); 12 | let desc2 = Miniscript::::from_str(&str2).unwrap(); 13 | 14 | assert_eq!(desc, desc2); 15 | } else if let Ok(desc) = Miniscript::::from_str(&s) { 16 | let str2 = desc.to_string(); 17 | let desc2: Miniscript = Miniscript::::from_str(&str2).unwrap(); 18 | 19 | assert_eq!(desc, desc2); 20 | } 21 | } 22 | 23 | fn main() { 24 | loop { 25 | fuzz!(|data| { 26 | do_test(data); 27 | }); 28 | } 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 34 | let mut b = 0; 35 | for (idx, c) in hex.as_bytes().iter().enumerate() { 36 | b <<= 4; 37 | match *c { 38 | b'A'..=b'F' => b |= c - b'A' + 10, 39 | b'a'..=b'f' => b |= c - b'a' + 10, 40 | b'0'..=b'9' => b |= c - b'0', 41 | _ => panic!("Bad hex"), 42 | } 43 | if (idx & 1) == 1 { 44 | out.push(b); 45 | b = 0; 46 | } 47 | } 48 | } 49 | 50 | #[test] 51 | fn duplicate_crash() { 52 | let mut a = Vec::new(); 53 | extend_vec_from_hex("1479002d00000020323731363342740000004000000000000000000000000000000000000063630004636363639c00000000000000000000", &mut a); 54 | super::do_test(&a); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_semantic.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] 2 | 3 | use std::str::FromStr; 4 | 5 | use honggfuzz::fuzz; 6 | use miniscript::policy; 7 | 8 | type Policy = policy::Semantic; 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 | let output = pol.to_string(); 14 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 15 | } 16 | } 17 | 18 | fn main() { 19 | loop { 20 | fuzz!(|data| { 21 | do_test(data); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fuzz/generate-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | REPO_DIR=$(git rev-parse --show-toplevel) 6 | 7 | # can't find the file because of the ENV var 8 | # shellcheck source=/dev/null 9 | source "$REPO_DIR/fuzz/fuzz-util.sh" 10 | 11 | # 1. Generate fuzz/Cargo.toml 12 | cat > "$REPO_DIR/fuzz/Cargo.toml" <> "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/cron-daily-fuzz.yml" <executed_\${{ matrix.fuzz_target }} 86 | - uses: actions/upload-artifact@v4 87 | with: 88 | name: executed_\${{ matrix.fuzz_target }} 89 | path: executed_\${{ matrix.fuzz_target }} 90 | 91 | verify-execution: 92 | if: \${{ !github.event.act }} 93 | needs: fuzz 94 | runs-on: ubuntu-latest 95 | steps: 96 | - uses: actions/checkout@v2 97 | - uses: actions/download-artifact@v4 98 | - name: Display structure of downloaded files 99 | run: ls -R 100 | - run: find executed_* -type f -exec cat {} + | sort > executed 101 | - run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed 102 | EOF 103 | 104 | -------------------------------------------------------------------------------- /fuzz/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Written in 2019 by Andrew Poelstra 2 | // SPDX-License-Identifier: CC0-1.0 3 | 4 | //! Miniscript Fuzzing Library 5 | //! 6 | //! This contains data structures and utilities used by the fuzz tests. 7 | 8 | use core::{fmt, str}; 9 | 10 | use miniscript::bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; 11 | use miniscript::bitcoin::{secp256k1, PublicKey}; 12 | use miniscript::{hash256, MiniscriptKey, ToPublicKey}; 13 | 14 | /// A public key which is encoded as a single hex byte (two hex characters). 15 | /// 16 | /// Implements `ToPublicKey` but (for now) always maps to the same bitcoin public key. 17 | #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)] 18 | pub struct FuzzPk { 19 | compressed: bool, 20 | } 21 | 22 | impl FuzzPk { 23 | pub fn new_from_control_byte(control: u8) -> Self { Self { compressed: control & 1 == 1 } } 24 | } 25 | 26 | impl str::FromStr for FuzzPk { 27 | type Err = std::num::ParseIntError; 28 | fn from_str(s: &str) -> Result { 29 | let byte = u8::from_str_radix(s, 16)?; 30 | Ok(Self::new_from_control_byte(byte)) 31 | } 32 | } 33 | 34 | impl fmt::Display for FuzzPk { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { "[fuzz pubkey]".fmt(f) } 36 | } 37 | 38 | impl MiniscriptKey for FuzzPk { 39 | type Sha256 = u8; 40 | type Ripemd160 = u8; 41 | type Hash160 = u8; 42 | type Hash256 = u8; 43 | } 44 | 45 | impl ToPublicKey for FuzzPk { 46 | fn to_public_key(&self) -> PublicKey { 47 | let secp_pk = secp256k1::PublicKey::from_slice(&[ 48 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x78, 49 | 0xce, 0x56, 0x3f, 0x89, 0xa0, 0xed, 0x94, 0x14, 0xf5, 0xaa, 0x28, 0xad, 0x0d, 0x96, 50 | 0xd6, 0x79, 0x5f, 0x9c, 0x63, 0x3f, 0x39, 0x79, 0xbf, 0x72, 0xae, 0x82, 0x02, 0x98, 51 | 0x3d, 0xc9, 0x89, 0xae, 0xc7, 0xf2, 0xff, 0x2e, 0xd9, 0x1b, 0xdd, 0x69, 0xce, 0x02, 52 | 0xfc, 0x07, 0x00, 0xca, 0x10, 0x0e, 0x59, 0xdd, 0xf3, 53 | ]) 54 | .unwrap(); 55 | PublicKey { inner: secp_pk, compressed: self.compressed } 56 | } 57 | 58 | fn to_sha256(hash: &Self::Sha256) -> sha256::Hash { sha256::Hash::from_byte_array([*hash; 32]) } 59 | 60 | fn to_hash256(hash: &Self::Hash256) -> hash256::Hash { 61 | hash256::Hash::from_byte_array([*hash; 32]) 62 | } 63 | 64 | fn to_ripemd160(hash: &Self::Ripemd160) -> ripemd160::Hash { 65 | ripemd160::Hash::from_byte_array([*hash; 20]) 66 | } 67 | 68 | fn to_hash160(hash: &Self::Ripemd160) -> hash160::Hash { 69 | hash160::Hash::from_byte_array([*hash; 20]) 70 | } 71 | } 72 | 73 | impl old_miniscript::MiniscriptKey for FuzzPk { 74 | type Sha256 = u8; 75 | type Ripemd160 = u8; 76 | type Hash160 = u8; 77 | type Hash256 = u8; 78 | } 79 | 80 | impl old_miniscript::ToPublicKey for FuzzPk { 81 | fn to_public_key(&self) -> PublicKey { 82 | let secp_pk = secp256k1::PublicKey::from_slice(&[ 83 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x78, 84 | 0xce, 0x56, 0x3f, 0x89, 0xa0, 0xed, 0x94, 0x14, 0xf5, 0xaa, 0x28, 0xad, 0x0d, 0x96, 85 | 0xd6, 0x79, 0x5f, 0x9c, 0x63, 0x3f, 0x39, 0x79, 0xbf, 0x72, 0xae, 0x82, 0x02, 0x98, 86 | 0x3d, 0xc9, 0x89, 0xae, 0xc7, 0xf2, 0xff, 0x2e, 0xd9, 0x1b, 0xdd, 0x69, 0xce, 0x02, 87 | 0xfc, 0x07, 0x00, 0xca, 0x10, 0x0e, 0x59, 0xdd, 0xf3, 88 | ]) 89 | .unwrap(); 90 | PublicKey { inner: secp_pk, compressed: self.compressed } 91 | } 92 | 93 | fn to_sha256(hash: &Self::Sha256) -> sha256::Hash { sha256::Hash::from_byte_array([*hash; 32]) } 94 | 95 | fn to_hash256(hash: &Self::Hash256) -> old_miniscript::hash256::Hash { 96 | old_miniscript::hash256::Hash::from_byte_array([*hash; 32]) 97 | } 98 | 99 | fn to_ripemd160(hash: &Self::Ripemd160) -> ripemd160::Hash { 100 | ripemd160::Hash::from_byte_array([*hash; 20]) 101 | } 102 | 103 | fn to_hash160(hash: &Self::Ripemd160) -> hash160::Hash { 104 | hash160::Hash::from_byte_array([*hash; 20]) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: 2 | @just --list 3 | 4 | # Cargo build everything. 5 | build: 6 | cargo build --all-targets --all-features 7 | 8 | # Cargo check everything. 9 | check: 10 | cargo check --all-targets --all-features 11 | 12 | # Lint everything. 13 | lint: 14 | cargo +nightly clippy --all-targets --all-features -- --deny warnings 15 | 16 | # Run the formatter. 17 | fmt: 18 | cargo +nightly fmt 19 | 20 | # Check the formatting. 21 | fmt-check: 22 | cargo +nightly fmt --check 23 | 24 | # Run the benchmark suite. 25 | bench: 26 | RUSTFLAGS='--cfg=bench' cargo +nightly bench benchmarks 27 | 28 | # Build the docs (same as for docs.rs). 29 | docsrs: 30 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly rustdoc --all-features -- -D rustdoc::broken-intra-doc-links 31 | 32 | # Quick and dirty CI useful for pre-push checks. 33 | sane: fmt-check lint 34 | cargo test --quiet --all-targets --no-default-features --features > /dev/null || exit 1 35 | cargo test --quiet --all-targets > /dev/null || exit 1 36 | cargo test --quiet --all-targets --all-features > /dev/null || exit 1 37 | 38 | # doctests don't get run from workspace root with `cargo test`. 39 | cargo test --quiet --doc || exit 1 40 | 41 | # Update the recent and minimal lock files. 42 | update-lock-files: 43 | contrib/update-lock-files.sh 44 | -------------------------------------------------------------------------------- /nightly-version: -------------------------------------------------------------------------------- 1 | nightly-2025-03-21 2 | -------------------------------------------------------------------------------- /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 | style_edition = "2021" 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 | show_parse_errors = true 69 | error_on_line_overflow = false 70 | error_on_unformatted = false 71 | ignore = [] 72 | emit_mode = "Files" 73 | make_backup = false 74 | -------------------------------------------------------------------------------- /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 | /// Auxiliary trait indicating that a type implements both `Debug`, `Display`, `Send` and `Sync`. 21 | // NOTE: `Send` / `Sync` is required to maintain compatibility with downstream error handling libraries. 22 | pub trait StaticDebugAndDisplay: fmt::Debug + fmt::Display + Send + Sync + 'static {} 23 | 24 | impl StaticDebugAndDisplay for T {} 25 | 26 | /// Blanket trait describing a key where all associated types implement `FromStr`, 27 | /// and all `FromStr` errors can be displayed. 28 | pub trait FromStrKey: 29 | MiniscriptKey< 30 | Sha256 = Self::_Sha256, 31 | Hash256 = Self::_Hash256, 32 | Ripemd160 = Self::_Ripemd160, 33 | Hash160 = Self::_Hash160, 34 | > + FromStr 35 | { 36 | /// Dummy type. Do not use. 37 | type _Sha256: FromStr 38 | + Clone 39 | + Eq 40 | + Ord 41 | + fmt::Display 42 | + fmt::Debug 43 | + hash::Hash; 44 | /// Dummy type. Do not use. 45 | type _Sha256FromStrErr: StaticDebugAndDisplay; 46 | /// Dummy type. Do not use. 47 | type _Hash256: FromStr 48 | + Clone 49 | + Eq 50 | + Ord 51 | + fmt::Display 52 | + fmt::Debug 53 | + hash::Hash; 54 | /// Dummy type. Do not use. 55 | type _Hash256FromStrErr: StaticDebugAndDisplay; 56 | /// Dummy type. Do not use. 57 | type _Ripemd160: FromStr 58 | + Clone 59 | + Eq 60 | + Ord 61 | + fmt::Display 62 | + fmt::Debug 63 | + hash::Hash; 64 | /// Dummy type. Do not use. 65 | type _Ripemd160FromStrErr: StaticDebugAndDisplay; 66 | /// Dummy type. Do not use. 67 | type _Hash160: FromStr 68 | + Clone 69 | + Eq 70 | + Ord 71 | + fmt::Display 72 | + fmt::Debug 73 | + hash::Hash; 74 | /// Dummy type. Do not use. 75 | type _Hash160FromStrErr: StaticDebugAndDisplay; 76 | /// Dummy type. Do not use. 77 | type _FromStrErr: StaticDebugAndDisplay; 78 | } 79 | 80 | impl FromStrKey for T 81 | where 82 | Self: MiniscriptKey + FromStr, 83 | ::Sha256: FromStr, 84 | Self::Hash256: FromStr, 85 | Self::Ripemd160: FromStr, 86 | Self::Hash160: FromStr, 87 | ::Err: StaticDebugAndDisplay, 88 | <::Sha256 as FromStr>::Err: StaticDebugAndDisplay, 89 | ::Err: StaticDebugAndDisplay, 90 | ::Err: StaticDebugAndDisplay, 91 | ::Err: StaticDebugAndDisplay, 92 | { 93 | type _Sha256 = ::Sha256; 94 | type _Sha256FromStrErr = <::Sha256 as FromStr>::Err; 95 | type _Hash256 = ::Hash256; 96 | type _Hash256FromStrErr = <::Hash256 as FromStr>::Err; 97 | type _Ripemd160 = ::Ripemd160; 98 | type _Ripemd160FromStrErr = <::Ripemd160 as FromStr>::Err; 99 | type _Hash160 = ::Hash160; 100 | type _Hash160FromStrErr = <::Hash160 as FromStr>::Err; 101 | type _FromStrErr = ::Err; 102 | } 103 | -------------------------------------------------------------------------------- /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 | 11 | use bitcoin::script; 12 | 13 | use crate::blanket_traits::FromStrKey; 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 | expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, Satisfier, 23 | Threshold, ToPublicKey, TranslateErr, Translator, 24 | }; 25 | 26 | /// Contents of a "sortedmulti" descriptor 27 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 | pub struct SortedMultiVec { 29 | inner: Threshold, 30 | /// The current ScriptContext for sortedmulti 31 | phantom: PhantomData, 32 | } 33 | 34 | impl SortedMultiVec { 35 | fn constructor_check(mut self) -> Result { 36 | // Check the limits before creating a new SortedMultiVec 37 | // For example, under p2sh context the scriptlen can only be 38 | // upto 520 bytes. 39 | let term: Terminal = Terminal::Multi(self.inner); 40 | let ms = Miniscript::from_ast(term)?; 41 | // This would check all the consensus rules for p2sh/p2wsh and 42 | // even tapscript in future 43 | Ctx::check_local_validity(&ms)?; 44 | if let Terminal::Multi(inner) = ms.node { 45 | self.inner = inner; 46 | Ok(self) 47 | } else { 48 | unreachable!() 49 | } 50 | } 51 | 52 | /// Create a new instance of `SortedMultiVec` given a list of keys and the threshold 53 | /// 54 | /// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`. 55 | pub fn new(k: usize, pks: Vec) -> Result { 56 | let ret = 57 | Self { inner: Threshold::new(k, pks).map_err(Error::Threshold)?, phantom: PhantomData }; 58 | ret.constructor_check() 59 | } 60 | 61 | /// Parse an expression tree into a SortedMultiVec 62 | pub fn from_tree(tree: expression::TreeIterItem) -> Result 63 | where 64 | Pk: FromStrKey, 65 | { 66 | tree.verify_toplevel("sortedmulti", 1..) 67 | .map_err(From::from) 68 | .map_err(Error::Parse)?; 69 | 70 | let ret = Self { 71 | inner: tree 72 | .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse))?, 73 | phantom: PhantomData, 74 | }; 75 | ret.constructor_check() 76 | } 77 | 78 | /// This will panic if fpk returns an uncompressed key when 79 | /// converting to a Segwit descriptor. To prevent this panic, ensure 80 | /// fpk returns an error in this case instead. 81 | pub fn translate_pk( 82 | &self, 83 | t: &mut T, 84 | ) -> Result, TranslateErr> 85 | where 86 | T: Translator, 87 | { 88 | let ret = SortedMultiVec { 89 | inner: self.inner.translate_ref(|pk| t.pk(pk))?, 90 | phantom: PhantomData, 91 | }; 92 | ret.constructor_check().map_err(TranslateErr::OuterError) 93 | } 94 | 95 | /// The threshold value for the multisig. 96 | pub fn k(&self) -> usize { self.inner.k() } 97 | 98 | /// The number of keys in the multisig. 99 | pub fn n(&self) -> usize { self.inner.n() } 100 | 101 | /// Accessor for the public keys in the multisig. 102 | /// 103 | /// The keys in this structure might **not** be sorted. In general, they cannot be 104 | /// sorted until they are converted to consensus-encoded public keys, which may not 105 | /// be possible (for example for BIP32 paths with unfilled wildcards). 106 | pub fn pks(&self) -> &[Pk] { self.inner.data() } 107 | } 108 | 109 | impl ForEachKey for SortedMultiVec { 110 | fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { 111 | self.pks().iter().all(pred) 112 | } 113 | } 114 | 115 | impl SortedMultiVec { 116 | /// utility function to sanity a sorted multi vec 117 | pub fn sanity_check(&self) -> Result<(), Error> { 118 | let ms: Miniscript = 119 | Miniscript::from_ast(Terminal::Multi(self.inner.clone())).expect("Must typecheck"); 120 | ms.sanity_check().map_err(From::from) 121 | } 122 | } 123 | 124 | impl SortedMultiVec { 125 | /// Create Terminal::Multi containing sorted pubkeys 126 | pub fn sorted_node(&self) -> Terminal 127 | where 128 | Pk: ToPublicKey, 129 | { 130 | let mut thresh = self.inner.clone(); 131 | // Sort pubkeys lexicographically according to BIP 67 132 | thresh.data_mut().sort_by(|a, b| { 133 | a.to_public_key() 134 | .inner 135 | .serialize() 136 | .partial_cmp(&b.to_public_key().inner.serialize()) 137 | .unwrap() 138 | }); 139 | Terminal::Multi(thresh) 140 | } 141 | 142 | /// Encode as a Bitcoin script 143 | pub fn encode(&self) -> script::ScriptBuf 144 | where 145 | Pk: ToPublicKey, 146 | { 147 | self.sorted_node() 148 | .encode(script::Builder::new()) 149 | .into_script() 150 | } 151 | 152 | /// Attempt to produce a satisfying witness for the 153 | /// witness script represented by the parse tree 154 | pub fn satisfy(&self, satisfier: S) -> Result>, Error> 155 | where 156 | Pk: ToPublicKey, 157 | S: Satisfier, 158 | { 159 | let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck"); 160 | ms.satisfy(satisfier) 161 | } 162 | 163 | /// Attempt to produce a witness template given the assets available 164 | pub fn build_template

(&self, provider: &P) -> Satisfaction> 165 | where 166 | Pk: ToPublicKey, 167 | P: AssetProvider, 168 | { 169 | let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck"); 170 | ms.build_template(provider) 171 | } 172 | 173 | /// Size, in bytes of the script-pubkey. If this Miniscript is used outside 174 | /// of segwit (e.g. in a bare or P2SH descriptor), this quantity should be 175 | /// multiplied by 4 to compute the weight. 176 | /// 177 | /// In general, it is not recommended to use this function directly, but 178 | /// to instead call the corresponding function on a `Descriptor`, which 179 | /// will handle the segwit/non-segwit technicalities for you. 180 | pub fn script_size(&self) -> usize { 181 | script_num_size(self.k()) 182 | + 1 183 | + script_num_size(self.n()) 184 | + self.pks().iter().map(|pk| Ctx::pk_len(pk)).sum::() 185 | } 186 | 187 | /// Maximum number of witness elements used to satisfy the Miniscript 188 | /// fragment, including the witness script itself. Used to estimate 189 | /// the weight of the `VarInt` that specifies this number in a serialized 190 | /// transaction. 191 | /// 192 | /// This function may panic on malformed `Miniscript` objects which do 193 | /// not correspond to semantically sane Scripts. (Such scripts should be 194 | /// rejected at parse time. Any exceptions are bugs.) 195 | pub fn max_satisfaction_witness_elements(&self) -> usize { 2 + self.k() } 196 | 197 | /// Maximum size, in bytes, of a satisfying witness. 198 | /// In general, it is not recommended to use this function directly, but 199 | /// to instead call the corresponding function on a `Descriptor`, which 200 | /// will handle the segwit/non-segwit technicalities for you. 201 | /// 202 | /// All signatures are assumed to be 73 bytes in size, including the 203 | /// length prefix (segwit) or push opcode (pre-segwit) and sighash 204 | /// postfix. 205 | pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k() } 206 | } 207 | 208 | impl policy::Liftable for SortedMultiVec { 209 | fn lift(&self) -> Result, Error> { 210 | Ok(policy::semantic::Policy::Thresh( 211 | self.inner 212 | .map_ref(|pk| Arc::new(policy::semantic::Policy::Key(pk.clone()))) 213 | .forget_maximum(), 214 | )) 215 | } 216 | } 217 | 218 | impl fmt::Debug for SortedMultiVec { 219 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } 220 | } 221 | 222 | impl fmt::Display for SortedMultiVec { 223 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 224 | fmt::Display::fmt(&self.inner.display("sortedmulti", true), f) 225 | } 226 | } 227 | 228 | #[cfg(test)] 229 | mod tests { 230 | use core::str::FromStr as _; 231 | 232 | use bitcoin::secp256k1::PublicKey; 233 | 234 | use super::*; 235 | use crate::miniscript::context::Legacy; 236 | 237 | #[test] 238 | fn too_many_pubkeys() { 239 | // Arbitrary pubic key. 240 | let pk = PublicKey::from_str( 241 | "02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443", 242 | ) 243 | .unwrap(); 244 | 245 | let over = 1 + MAX_PUBKEYS_PER_MULTISIG; 246 | 247 | let mut pks = Vec::new(); 248 | for _ in 0..over { 249 | pks.push(pk); 250 | } 251 | 252 | let res: Result, Error> = SortedMultiVec::new(0, pks); 253 | let error = res.expect_err("constructor should err"); 254 | 255 | match error { 256 | Error::Threshold(_) => {} // ok 257 | other => panic!("unexpected error: {:?}", other), 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Written in 2019 by Andrew Poelstra 2 | // SPDX-License-Identifier: CC0-1.0 3 | 4 | //! Errors 5 | 6 | use core::fmt; 7 | #[cfg(feature = "std")] 8 | use std::error; 9 | 10 | use crate::blanket_traits::StaticDebugAndDisplay; 11 | use crate::primitives::absolute_locktime::AbsLockTimeError; 12 | use crate::primitives::relative_locktime::RelLockTimeError; 13 | use crate::Box; 14 | 15 | /// An error parsing a Miniscript object (policy, descriptor or miniscript) 16 | /// from a string. 17 | #[derive(Debug)] 18 | pub enum ParseError { 19 | /// Invalid absolute locktime 20 | AbsoluteLockTime(AbsLockTimeError), 21 | /// Invalid relative locktime 22 | RelativeLockTime(RelLockTimeError), 23 | /// Failed to parse a public key or hash. 24 | /// 25 | /// Note that the error information is lost for nostd compatibility reasons. See 26 | /// . 27 | FromStr(Box), 28 | /// Failed to parse a number. 29 | Num(crate::ParseNumError), 30 | /// Error parsing a string into an expression tree. 31 | Tree(crate::ParseTreeError), 32 | } 33 | 34 | impl ParseError { 35 | /// Boxes a `FromStr` error for a `Pk` (or associated types) into a `ParseError` 36 | pub(crate) fn box_from_str(e: E) -> Self { 37 | ParseError::FromStr(Box::new(e)) 38 | } 39 | } 40 | 41 | impl From for ParseError { 42 | fn from(e: crate::ParseNumError) -> Self { Self::Num(e) } 43 | } 44 | 45 | impl From for ParseError { 46 | fn from(e: crate::ParseTreeError) -> Self { Self::Tree(e) } 47 | } 48 | 49 | impl fmt::Display for ParseError { 50 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 51 | match self { 52 | ParseError::AbsoluteLockTime(ref e) => e.fmt(f), 53 | ParseError::RelativeLockTime(ref e) => e.fmt(f), 54 | ParseError::FromStr(ref e) => e.fmt(f), 55 | ParseError::Num(ref e) => e.fmt(f), 56 | ParseError::Tree(ref e) => e.fmt(f), 57 | } 58 | } 59 | } 60 | 61 | #[cfg(feature = "std")] 62 | impl error::Error for ParseError { 63 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 64 | match self { 65 | ParseError::AbsoluteLockTime(ref e) => Some(e), 66 | ParseError::RelativeLockTime(ref e) => Some(e), 67 | ParseError::FromStr(..) => None, 68 | ParseError::Num(ref e) => Some(e), 69 | ParseError::Tree(ref e) => Some(e), 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /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 | type NaryChildren = &'a [Arc>]; 22 | 23 | fn nary_len(tc: &Self::NaryChildren) -> usize { tc.len() } 24 | fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self { Arc::as_ref(&tc[idx]) } 25 | 26 | fn as_node(&self) -> Tree { 27 | use Terminal::*; 28 | match self.node { 29 | PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..) 30 | | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary, 31 | Alt(ref sub) 32 | | Swap(ref sub) 33 | | Check(ref sub) 34 | | DupIf(ref sub) 35 | | Verify(ref sub) 36 | | NonZero(ref sub) 37 | | ZeroNotEqual(ref sub) => Tree::Unary(sub), 38 | AndV(ref left, ref right) 39 | | AndB(ref left, ref right) 40 | | OrB(ref left, ref right) 41 | | OrD(ref left, ref right) 42 | | OrC(ref left, ref right) 43 | | OrI(ref left, ref right) => Tree::Binary(left, right), 44 | AndOr(ref a, ref b, ref c) => Tree::Ternary(a, b, c), 45 | Thresh(ref thresh) => Tree::Nary(thresh.data()), 46 | } 47 | } 48 | } 49 | 50 | impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc> { 51 | type NaryChildren = &'a [Arc>]; 52 | 53 | fn nary_len(tc: &Self::NaryChildren) -> usize { tc.len() } 54 | fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self { &tc[idx] } 55 | 56 | fn as_node(&self) -> Tree { 57 | use Terminal::*; 58 | match self.node { 59 | PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..) 60 | | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary, 61 | Alt(ref sub) 62 | | Swap(ref sub) 63 | | Check(ref sub) 64 | | DupIf(ref sub) 65 | | Verify(ref sub) 66 | | NonZero(ref sub) 67 | | ZeroNotEqual(ref sub) => Tree::Unary(sub), 68 | AndV(ref left, ref right) 69 | | AndB(ref left, ref right) 70 | | OrB(ref left, ref right) 71 | | OrD(ref left, ref right) 72 | | OrC(ref left, ref right) 73 | | OrI(ref left, ref right) => Tree::Binary(left, right), 74 | AndOr(ref a, ref b, ref c) => Tree::Ternary(a, b, c), 75 | Thresh(ref thresh) => Tree::Nary(thresh.data()), 76 | } 77 | } 78 | } 79 | 80 | impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal { 81 | type NaryChildren = &'a [Arc>]; 82 | 83 | fn nary_len(tc: &Self::NaryChildren) -> usize { tc.len() } 84 | fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self { tc[idx].as_inner() } 85 | 86 | fn as_node(&self) -> Tree { 87 | use Terminal::*; 88 | match self { 89 | PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..) 90 | | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary, 91 | Alt(ref sub) 92 | | Swap(ref sub) 93 | | Check(ref sub) 94 | | DupIf(ref sub) 95 | | Verify(ref sub) 96 | | NonZero(ref sub) 97 | | ZeroNotEqual(ref sub) => Tree::Unary(sub.as_inner()), 98 | AndV(ref left, ref right) 99 | | AndB(ref left, ref right) 100 | | OrB(ref left, ref right) 101 | | OrD(ref left, ref right) 102 | | OrC(ref left, ref right) 103 | | OrI(ref left, ref right) => Tree::Binary(left.as_inner(), right.as_inner()), 104 | AndOr(ref a, ref b, ref c) => Tree::Ternary(a.as_inner(), b.as_inner(), c.as_inner()), 105 | Thresh(ref thresh) => Tree::Nary(thresh.data()), 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/miniscript/astelem.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! AST Elements 4 | //! 5 | //! Datatype describing a Miniscript "script fragment", which are the 6 | //! building blocks of all Miniscripts. Each fragment has a unique 7 | //! encoding in Bitcoin script, as well as a datatype. Full details 8 | //! are given on the Miniscript website. 9 | 10 | use bitcoin::hashes::Hash; 11 | use bitcoin::{absolute, opcodes, script}; 12 | 13 | use crate::miniscript::context::SigType; 14 | use crate::miniscript::ScriptContext; 15 | use crate::util::MsKeyBuilder; 16 | use crate::{Miniscript, MiniscriptKey, Terminal, ToPublicKey}; 17 | 18 | /// Helper trait to add a `push_astelem` method to `script::Builder` 19 | trait PushAstElem { 20 | fn push_astelem(self, ast: &Miniscript) -> Self 21 | where 22 | Pk: ToPublicKey; 23 | } 24 | 25 | impl PushAstElem for script::Builder { 26 | fn push_astelem(self, ast: &Miniscript) -> Self 27 | where 28 | Pk: ToPublicKey, 29 | { 30 | ast.node.encode(self) 31 | } 32 | } 33 | 34 | impl Terminal { 35 | /// Encode the element as a fragment of Bitcoin Script. The inverse 36 | /// function, from Script to an AST element, is implemented in the 37 | /// `parse` module. 38 | pub fn encode(&self, mut builder: script::Builder) -> script::Builder 39 | where 40 | Pk: ToPublicKey, 41 | { 42 | match *self { 43 | Terminal::PkK(ref pk) => builder.push_ms_key::<_, Ctx>(pk), 44 | Terminal::PkH(ref pk) => builder 45 | .push_opcode(opcodes::all::OP_DUP) 46 | .push_opcode(opcodes::all::OP_HASH160) 47 | .push_ms_key_hash::<_, Ctx>(pk) 48 | .push_opcode(opcodes::all::OP_EQUALVERIFY), 49 | Terminal::RawPkH(ref hash) => builder 50 | .push_opcode(opcodes::all::OP_DUP) 51 | .push_opcode(opcodes::all::OP_HASH160) 52 | .push_slice(hash.to_byte_array()) 53 | .push_opcode(opcodes::all::OP_EQUALVERIFY), 54 | Terminal::After(t) => builder 55 | .push_int(absolute::LockTime::from(t).to_consensus_u32() as i64) 56 | .push_opcode(opcodes::all::OP_CLTV), 57 | Terminal::Older(t) => builder 58 | .push_int(t.to_consensus_u32().into()) 59 | .push_opcode(opcodes::all::OP_CSV), 60 | Terminal::Sha256(ref h) => builder 61 | .push_opcode(opcodes::all::OP_SIZE) 62 | .push_int(32) 63 | .push_opcode(opcodes::all::OP_EQUALVERIFY) 64 | .push_opcode(opcodes::all::OP_SHA256) 65 | .push_slice(Pk::to_sha256(h).to_byte_array()) 66 | .push_opcode(opcodes::all::OP_EQUAL), 67 | Terminal::Hash256(ref h) => builder 68 | .push_opcode(opcodes::all::OP_SIZE) 69 | .push_int(32) 70 | .push_opcode(opcodes::all::OP_EQUALVERIFY) 71 | .push_opcode(opcodes::all::OP_HASH256) 72 | .push_slice(Pk::to_hash256(h).to_byte_array()) 73 | .push_opcode(opcodes::all::OP_EQUAL), 74 | Terminal::Ripemd160(ref h) => builder 75 | .push_opcode(opcodes::all::OP_SIZE) 76 | .push_int(32) 77 | .push_opcode(opcodes::all::OP_EQUALVERIFY) 78 | .push_opcode(opcodes::all::OP_RIPEMD160) 79 | .push_slice(Pk::to_ripemd160(h).to_byte_array()) 80 | .push_opcode(opcodes::all::OP_EQUAL), 81 | Terminal::Hash160(ref h) => builder 82 | .push_opcode(opcodes::all::OP_SIZE) 83 | .push_int(32) 84 | .push_opcode(opcodes::all::OP_EQUALVERIFY) 85 | .push_opcode(opcodes::all::OP_HASH160) 86 | .push_slice(Pk::to_hash160(h).to_byte_array()) 87 | .push_opcode(opcodes::all::OP_EQUAL), 88 | Terminal::True => builder.push_opcode(opcodes::OP_TRUE), 89 | Terminal::False => builder.push_opcode(opcodes::OP_FALSE), 90 | Terminal::Alt(ref sub) => builder 91 | .push_opcode(opcodes::all::OP_TOALTSTACK) 92 | .push_astelem(sub) 93 | .push_opcode(opcodes::all::OP_FROMALTSTACK), 94 | Terminal::Swap(ref sub) => builder.push_opcode(opcodes::all::OP_SWAP).push_astelem(sub), 95 | Terminal::Check(ref sub) => builder 96 | .push_astelem(sub) 97 | .push_opcode(opcodes::all::OP_CHECKSIG), 98 | Terminal::DupIf(ref sub) => builder 99 | .push_opcode(opcodes::all::OP_DUP) 100 | .push_opcode(opcodes::all::OP_IF) 101 | .push_astelem(sub) 102 | .push_opcode(opcodes::all::OP_ENDIF), 103 | Terminal::Verify(ref sub) => builder.push_astelem(sub).push_verify(), 104 | Terminal::NonZero(ref sub) => builder 105 | .push_opcode(opcodes::all::OP_SIZE) 106 | .push_opcode(opcodes::all::OP_0NOTEQUAL) 107 | .push_opcode(opcodes::all::OP_IF) 108 | .push_astelem(sub) 109 | .push_opcode(opcodes::all::OP_ENDIF), 110 | Terminal::ZeroNotEqual(ref sub) => builder 111 | .push_astelem(sub) 112 | .push_opcode(opcodes::all::OP_0NOTEQUAL), 113 | Terminal::AndV(ref left, ref right) => builder.push_astelem(left).push_astelem(right), 114 | Terminal::AndB(ref left, ref right) => builder 115 | .push_astelem(left) 116 | .push_astelem(right) 117 | .push_opcode(opcodes::all::OP_BOOLAND), 118 | Terminal::AndOr(ref a, ref b, ref c) => builder 119 | .push_astelem(a) 120 | .push_opcode(opcodes::all::OP_NOTIF) 121 | .push_astelem(c) 122 | .push_opcode(opcodes::all::OP_ELSE) 123 | .push_astelem(b) 124 | .push_opcode(opcodes::all::OP_ENDIF), 125 | Terminal::OrB(ref left, ref right) => builder 126 | .push_astelem(left) 127 | .push_astelem(right) 128 | .push_opcode(opcodes::all::OP_BOOLOR), 129 | Terminal::OrD(ref left, ref right) => builder 130 | .push_astelem(left) 131 | .push_opcode(opcodes::all::OP_IFDUP) 132 | .push_opcode(opcodes::all::OP_NOTIF) 133 | .push_astelem(right) 134 | .push_opcode(opcodes::all::OP_ENDIF), 135 | Terminal::OrC(ref left, ref right) => builder 136 | .push_astelem(left) 137 | .push_opcode(opcodes::all::OP_NOTIF) 138 | .push_astelem(right) 139 | .push_opcode(opcodes::all::OP_ENDIF), 140 | Terminal::OrI(ref left, ref right) => builder 141 | .push_opcode(opcodes::all::OP_IF) 142 | .push_astelem(left) 143 | .push_opcode(opcodes::all::OP_ELSE) 144 | .push_astelem(right) 145 | .push_opcode(opcodes::all::OP_ENDIF), 146 | Terminal::Thresh(ref thresh) => { 147 | builder = builder.push_astelem(&thresh.data()[0]); 148 | for sub in &thresh.data()[1..] { 149 | builder = builder.push_astelem(sub).push_opcode(opcodes::all::OP_ADD); 150 | } 151 | builder 152 | .push_int(thresh.k() as i64) 153 | .push_opcode(opcodes::all::OP_EQUAL) 154 | } 155 | Terminal::Multi(ref thresh) => { 156 | debug_assert!(Ctx::sig_type() == SigType::Ecdsa); 157 | builder = builder.push_int(thresh.k() as i64); 158 | for pk in thresh.data() { 159 | builder = builder.push_key(&pk.to_public_key()); 160 | } 161 | builder 162 | .push_int(thresh.n() as i64) 163 | .push_opcode(opcodes::all::OP_CHECKMULTISIG) 164 | } 165 | Terminal::MultiA(ref thresh) => { 166 | debug_assert!(Ctx::sig_type() == SigType::Schnorr); 167 | // keys must be atleast len 1 here, guaranteed by typing rules 168 | builder = builder.push_ms_key::<_, Ctx>(&thresh.data()[0]); 169 | builder = builder.push_opcode(opcodes::all::OP_CHECKSIG); 170 | for pk in thresh.iter().skip(1) { 171 | builder = builder.push_ms_key::<_, Ctx>(pk); 172 | builder = builder.push_opcode(opcodes::all::OP_CHECKSIGADD); 173 | } 174 | builder 175 | .push_int(thresh.k() as i64) 176 | .push_opcode(opcodes::all::OP_NUMEQUAL) 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /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 | /// Maximum pubkeys in a CHECKSIGADD construction. 37 | // https://github.com/bitcoin/bitcoin/blob/99b06b7f1d4194fb8036b90e5308101645f968e7/src/script/script.h#L36 38 | pub const MAX_PUBKEYS_IN_CHECKSIGADD: usize = 999; 39 | -------------------------------------------------------------------------------- /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 = 0x7FFF_FFFF; 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/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 | pub mod threshold; 17 | -------------------------------------------------------------------------------- /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() && seq != Sequence::ZERO { 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 | -------------------------------------------------------------------------------- /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 | /// type TargetPk = bitcoin::PublicKey; 32 | /// type Error = (); 33 | /// 34 | /// // Provides the translation public keys P -> Q 35 | /// fn pk(&mut self, pk: &String) -> Result { 36 | /// self.pk_map.get(pk).copied().ok_or(()) // Dummy Err 37 | /// } 38 | /// 39 | /// // Fail for hash types 40 | /// translate_hash_fail!(String, bitcoin::PublicKey, ()); 41 | /// } 42 | /// 43 | /// let mut pk_map = HashMap::new(); 44 | /// pk_map.insert(String::from("alice_key"), bitcoin::PublicKey::from_str(alice_key).unwrap()); 45 | /// pk_map.insert(String::from("bob_key"), bitcoin::PublicKey::from_str(bob_key).unwrap()); 46 | /// let mut t = StrPkTranslator { pk_map: pk_map }; 47 | /// ``` 48 | #[macro_export] 49 | macro_rules! translate_hash_fail { 50 | ($source: ty, $target:ty, $error_ty: ty) => { 51 | fn sha256( 52 | &mut self, 53 | _sha256: &<$source as $crate::MiniscriptKey>::Sha256, 54 | ) -> Result<<$target as $crate::MiniscriptKey>::Sha256, $error_ty> { 55 | panic!("Called sha256 on translate_only_pk") 56 | } 57 | 58 | fn hash256( 59 | &mut self, 60 | _hash256: &<$source as $crate::MiniscriptKey>::Hash256, 61 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash256, $error_ty> { 62 | panic!("Called hash256 on translate_only_pk") 63 | } 64 | 65 | fn hash160( 66 | &mut self, 67 | _hash160: &<$source as $crate::MiniscriptKey>::Hash160, 68 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash160, $error_ty> { 69 | panic!("Called hash160 on translate_only_pk") 70 | } 71 | 72 | fn ripemd160( 73 | &mut self, 74 | _ripemd160: &<$source as $crate::MiniscriptKey>::Ripemd160, 75 | ) -> Result<<$target as $crate::MiniscriptKey>::Ripemd160, $error_ty> { 76 | panic!("Called ripemd160 on translate_only_pk") 77 | } 78 | }; 79 | } 80 | 81 | /// Macro for translation of associated types where the associated type is the same 82 | /// Handy for Derived -> concrete keys where the associated types are the same. 83 | /// 84 | /// Writing the complete translator trait is tedious. This macro is handy when 85 | /// we are not trying the associated types for hash160, ripemd160, hash256 and 86 | /// sha256. 87 | /// 88 | /// See also [`crate::translate_hash_fail`] 89 | #[macro_export] 90 | macro_rules! translate_hash_clone { 91 | ($source: ty, $target:ty, $error_ty: ty) => { 92 | fn sha256( 93 | &mut self, 94 | sha256: &<$source as $crate::MiniscriptKey>::Sha256, 95 | ) -> Result<<$target as $crate::MiniscriptKey>::Sha256, $error_ty> { 96 | Ok((*sha256).into()) 97 | } 98 | 99 | fn hash256( 100 | &mut self, 101 | hash256: &<$source as $crate::MiniscriptKey>::Hash256, 102 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash256, $error_ty> { 103 | Ok((*hash256).into()) 104 | } 105 | 106 | fn hash160( 107 | &mut self, 108 | hash160: &<$source as $crate::MiniscriptKey>::Hash160, 109 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash160, $error_ty> { 110 | Ok((*hash160).into()) 111 | } 112 | 113 | fn ripemd160( 114 | &mut self, 115 | ripemd160: &<$source as $crate::MiniscriptKey>::Ripemd160, 116 | ) -> Result<<$target as $crate::MiniscriptKey>::Ripemd160, $error_ty> { 117 | Ok((*ripemd160).into()) 118 | } 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Generally useful utilities for test scripts 4 | 5 | use core::convert::Infallible; 6 | use std::collections::HashMap; 7 | use std::str::FromStr; 8 | 9 | use bitcoin::hashes::{hash160, ripemd160, sha256}; 10 | use bitcoin::key::XOnlyPublicKey; 11 | #[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684 12 | use bitcoin::secp256k1; 13 | 14 | use crate::miniscript::context::SigType; 15 | use crate::{hash256, ToPublicKey, Translator}; 16 | 17 | /// Translate from a String MiniscriptKey type to bitcoin::PublicKey 18 | /// If the hashmap is populated, this will lookup for keys in HashMap 19 | /// Otherwise, this will return a translation to a random key 20 | #[derive(Debug, PartialEq, Eq, Clone)] 21 | pub struct StrKeyTranslator { 22 | pub pk_map: HashMap, 23 | pub pkh_map: HashMap, 24 | pub sha256_map: HashMap, 25 | pub ripemd160_map: HashMap, 26 | pub hash160_map: HashMap, 27 | } 28 | 29 | impl Translator for StrKeyTranslator { 30 | type TargetPk = bitcoin::PublicKey; 31 | type Error = Infallible; 32 | 33 | fn pk(&mut self, pk: &String) -> Result { 34 | let key = self.pk_map.get(pk).copied().unwrap_or_else(|| { 35 | bitcoin::PublicKey::from_str( 36 | "02c2122e30e73f7fe37986e3f81ded00158e94b7ad472369b83bbdd28a9a198a39", 37 | ) 38 | .unwrap() 39 | }); 40 | Ok(key) 41 | } 42 | 43 | fn sha256(&mut self, _sha256: &String) -> Result { 44 | let hash = sha256::Hash::from_str( 45 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 46 | ) 47 | .unwrap(); 48 | Ok(hash) 49 | } 50 | 51 | fn hash256(&mut self, _hash256: &String) -> Result { 52 | // hard coded value 53 | let hash = hash256::Hash::from_str( 54 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 55 | ) 56 | .unwrap(); 57 | Ok(hash) 58 | } 59 | 60 | fn ripemd160(&mut self, _ripemd160: &String) -> Result { 61 | let hash = ripemd160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 62 | Ok(hash) 63 | } 64 | 65 | fn hash160(&mut self, _hash160: &String) -> Result { 66 | let hash = hash160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 67 | Ok(hash) 68 | } 69 | } 70 | 71 | /// Same as [`StrKeyTranslator`], but for [`bitcoin::XOnlyPublicKey`] 72 | #[derive(Debug, PartialEq, Eq, Clone)] 73 | pub struct StrXOnlyKeyTranslator { 74 | pub pk_map: HashMap, 75 | pub pkh_map: HashMap, 76 | pub sha256_map: HashMap, 77 | pub ripemd160_map: HashMap, 78 | pub hash160_map: HashMap, 79 | } 80 | 81 | impl Translator for StrXOnlyKeyTranslator { 82 | type TargetPk = XOnlyPublicKey; 83 | type Error = Infallible; 84 | 85 | fn pk(&mut self, pk: &String) -> Result { 86 | let key = self.pk_map.get(pk).copied().unwrap_or_else(|| { 87 | XOnlyPublicKey::from_str( 88 | "c2122e30e73f7fe37986e3f81ded00158e94b7ad472369b83bbdd28a9a198a39", 89 | ) 90 | .unwrap() 91 | }); 92 | Ok(key) 93 | } 94 | 95 | fn sha256(&mut self, _sha256: &String) -> Result { 96 | let hash = sha256::Hash::from_str( 97 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 98 | ) 99 | .unwrap(); 100 | Ok(hash) 101 | } 102 | 103 | fn hash256(&mut self, _hash256: &String) -> Result { 104 | let hash = hash256::Hash::from_str( 105 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 106 | ) 107 | .unwrap(); 108 | Ok(hash) 109 | } 110 | 111 | fn ripemd160(&mut self, _ripemd160: &String) -> Result { 112 | let hash = ripemd160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 113 | Ok(hash) 114 | } 115 | 116 | fn hash160(&mut self, _hash160: &String) -> Result { 117 | let hash = hash160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 118 | Ok(hash) 119 | } 120 | } 121 | 122 | // Deterministically sample keys to allow reproducible tests 123 | fn random_sks(n: usize) -> Vec { 124 | let mut sk = [0; 32]; 125 | let mut sks = vec![]; 126 | for i in 1..n + 1 { 127 | sk[0] = i as u8; 128 | sk[1] = (i >> 8) as u8; 129 | sk[2] = (i >> 16) as u8; 130 | sk[3] = (i >> 24) as u8; 131 | 132 | let sk = secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key"); 133 | sks.push(sk) 134 | } 135 | sks 136 | } 137 | 138 | impl StrKeyTranslator { 139 | pub fn new() -> Self { 140 | let secp = secp256k1::Secp256k1::new(); 141 | let sks = random_sks(26); 142 | let pks: Vec<_> = sks 143 | .iter() 144 | .map(|sk| bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, sk))) 145 | .collect(); 146 | let mut pk_map = HashMap::new(); 147 | let mut pkh_map = HashMap::new(); 148 | for (i, c) in (b'A'..=b'Z').enumerate() { 149 | let key = String::from_utf8(vec![c]).unwrap(); 150 | pk_map.insert(key.clone(), pks[i]); 151 | pkh_map.insert(key, pks[i].to_pubkeyhash(SigType::Ecdsa)); 152 | } 153 | // We don't bother filling in sha256_map preimages in default implementation as it not unnecessary 154 | // for sane miniscripts 155 | Self { 156 | pk_map, 157 | pkh_map, 158 | sha256_map: HashMap::new(), 159 | ripemd160_map: HashMap::new(), 160 | hash160_map: HashMap::new(), 161 | } 162 | } 163 | } 164 | 165 | impl StrXOnlyKeyTranslator { 166 | pub fn new() -> Self { 167 | let secp = secp256k1::Secp256k1::new(); 168 | let sks = random_sks(26); 169 | let pks: Vec<_> = sks 170 | .iter() 171 | .map(|sk| { 172 | let keypair = secp256k1::Keypair::from_secret_key(&secp, sk); 173 | let (pk, _parity) = XOnlyPublicKey::from_keypair(&keypair); 174 | pk 175 | }) 176 | .collect(); 177 | let mut pk_map = HashMap::new(); 178 | let mut pkh_map = HashMap::new(); 179 | for (i, c) in (b'A'..=b'Z').enumerate() { 180 | let key = String::from_utf8(vec![c]).unwrap(); 181 | pk_map.insert(key.clone(), pks[i]); 182 | pkh_map.insert(key, pks[i].to_pubkeyhash(SigType::Schnorr)); 183 | } 184 | // We don't bother filling in sha256_map preimages in default implementation as it not unnecessary 185 | // for sane miniscripts 186 | Self { 187 | pk_map, 188 | pkh_map, 189 | sha256_map: HashMap::new(), 190 | ripemd160_map: HashMap::new(), 191 | hash160_map: HashMap::new(), 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | use core::convert::TryFrom; 4 | 5 | use bitcoin::constants::MAX_SCRIPT_ELEMENT_SIZE; 6 | use bitcoin::hashes::Hash; 7 | use bitcoin::script::{self, PushBytes, ScriptBuf}; 8 | use bitcoin::PubkeyHash; 9 | 10 | use crate::miniscript::context; 11 | use crate::miniscript::satisfy::Placeholder; 12 | use crate::prelude::*; 13 | use crate::{MiniscriptKey, ScriptContext, ToPublicKey}; 14 | pub(crate) fn varint_len(n: usize) -> usize { bitcoin::VarInt(n as u64).size() } 15 | 16 | pub(crate) trait ItemSize { 17 | fn size(&self) -> usize; 18 | } 19 | 20 | impl ItemSize for Placeholder { 21 | fn size(&self) -> usize { 22 | match self { 23 | Placeholder::Pubkey(_, size) => *size, 24 | Placeholder::PubkeyHash(_, size) => *size, 25 | Placeholder::EcdsaSigPk(_) | Placeholder::EcdsaSigPkHash(_) => 73, 26 | Placeholder::SchnorrSigPk(_, _, size) | Placeholder::SchnorrSigPkHash(_, _, size) => { 27 | size + 1 28 | } // +1 for the OP_PUSH 29 | Placeholder::HashDissatisfaction 30 | | Placeholder::Sha256Preimage(_) 31 | | Placeholder::Hash256Preimage(_) 32 | | Placeholder::Ripemd160Preimage(_) 33 | | Placeholder::Hash160Preimage(_) => 33, 34 | Placeholder::PushOne => 2, // On legacy this should be 1 ? 35 | Placeholder::PushZero => 1, 36 | Placeholder::TapScript(s) => s.len(), 37 | Placeholder::TapControlBlock(cb) => cb.serialize().len(), 38 | } 39 | } 40 | } 41 | 42 | impl ItemSize for Vec { 43 | fn size(&self) -> usize { self.len() } 44 | } 45 | 46 | // Helper function to calculate witness size 47 | pub(crate) fn witness_size(wit: &[T]) -> usize { 48 | wit.iter().map(T::size).sum::() + varint_len(wit.len()) 49 | } 50 | 51 | pub(crate) fn witness_to_scriptsig(witness: &[Vec]) -> ScriptBuf { 52 | let mut b = script::Builder::new(); 53 | for (i, wit) in witness.iter().enumerate() { 54 | if let Ok(n) = script::read_scriptint(wit) { 55 | b = b.push_int(n); 56 | } else { 57 | if i != witness.len() - 1 { 58 | assert!(wit.len() < 73, "All pushes in miniscript are < 73 bytes"); 59 | } else { 60 | assert!(wit.len() <= MAX_SCRIPT_ELEMENT_SIZE, "P2SH redeem script is <= 520 bytes"); 61 | } 62 | let push = <&PushBytes>::try_from(wit.as_slice()).expect("checked above"); 63 | b = b.push_slice(push) 64 | } 65 | } 66 | b.into_script() 67 | } 68 | 69 | // trait for pushing key that depend on context 70 | pub(crate) trait MsKeyBuilder { 71 | /// Serialize the key as bytes based on script context. Used when encoding miniscript into bitcoin script 72 | fn push_ms_key(self, key: &Pk) -> Self 73 | where 74 | Pk: ToPublicKey, 75 | Ctx: ScriptContext; 76 | 77 | /// Serialize the key hash as bytes based on script context. Used when encoding miniscript into bitcoin script 78 | fn push_ms_key_hash(self, key: &Pk) -> Self 79 | where 80 | Pk: ToPublicKey, 81 | Ctx: ScriptContext; 82 | } 83 | 84 | impl MsKeyBuilder for script::Builder { 85 | fn push_ms_key(self, key: &Pk) -> Self 86 | where 87 | Pk: ToPublicKey, 88 | Ctx: ScriptContext, 89 | { 90 | match Ctx::sig_type() { 91 | context::SigType::Ecdsa => self.push_key(&key.to_public_key()), 92 | context::SigType::Schnorr => self.push_slice(key.to_x_only_pubkey().serialize()), 93 | } 94 | } 95 | 96 | fn push_ms_key_hash(self, key: &Pk) -> Self 97 | where 98 | Pk: ToPublicKey, 99 | Ctx: ScriptContext, 100 | { 101 | match Ctx::sig_type() { 102 | context::SigType::Ecdsa => self.push_slice(key.to_public_key().pubkey_hash()), 103 | context::SigType::Schnorr => { 104 | self.push_slice(PubkeyHash::hash(&key.to_x_only_pubkey().serialize())) 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------