├── .github └── workflows │ ├── fuzz.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── bitcoind-tests ├── Cargo.toml ├── bin │ ├── bitcoind │ └── elementsd └── tests │ ├── data │ └── random_ms.txt │ ├── setup │ ├── mod.rs │ └── test_util.rs │ ├── test_arith.rs │ ├── test_cpp.rs │ ├── test_csfs.rs │ ├── test_desc.rs │ └── test_introspect.rs ├── clippy.toml ├── contrib └── test.sh ├── doc ├── ReasoningAboutMultipartyMiniscript.pdf ├── Tr Compiler.pdf ├── compiler.md ├── extension_spec.md ├── resource_limitations.md └── security_report_2022_04_20.md ├── embedded └── src │ └── main.rs ├── examples ├── htlc.rs ├── parse.rs ├── psbt_sign_finalize.rs ├── sign_multisig.rs ├── taproot.rs ├── verify_tx.rs └── xpub_descriptors.rs ├── fuzz ├── Cargo.toml ├── README.md ├── cycle.sh ├── fuzz-util.sh ├── fuzz.sh ├── fuzz_targets │ ├── compile_descriptor.rs │ ├── parse_descriptor.rs │ ├── parse_descriptor_secret.rs │ ├── roundtrip_concrete.rs │ ├── roundtrip_confidential.rs │ ├── roundtrip_descriptor.rs │ ├── roundtrip_miniscript_script.rs │ ├── roundtrip_miniscript_str.rs │ └── roundtrip_semantic.rs └── generate-files.sh ├── rustfmt.toml └── src ├── confidential ├── bare.rs ├── elip151.rs ├── mod.rs └── slip77.rs ├── descriptor ├── bare.rs ├── checksum.rs ├── csfs_cov │ ├── cov.rs │ ├── error.rs │ ├── mod.rs │ ├── satisfy.rs │ └── script_internals.rs ├── key.rs ├── mod.rs ├── pegin │ ├── dynafed_pegin.rs │ ├── legacy_pegin.rs │ └── mod.rs ├── pretaproot.rs ├── segwitv0.rs ├── sh.rs ├── sortedmulti.rs └── tr.rs ├── expression.rs ├── extensions ├── arith.rs ├── csfs.rs ├── index_ops.rs ├── introspect_ops.rs ├── mod.rs ├── outputs_pref.rs ├── param.rs └── tx_ver.rs ├── interpreter ├── error.rs ├── inner.rs ├── mod.rs └── stack.rs ├── lib.rs ├── macros.rs ├── miniscript ├── analyzable.rs ├── astelem.rs ├── context.rs ├── decode.rs ├── iter.rs ├── lex.rs ├── limits.rs ├── mod.rs ├── ms_tests.rs ├── satisfy.rs └── types │ ├── correctness.rs │ ├── extra_props.rs │ ├── malleability.rs │ └── mod.rs ├── policy ├── compiler.rs ├── concrete.rs ├── mod.rs └── semantic.rs ├── psbt ├── finalizer.rs └── mod.rs ├── pub_macros.rs ├── simplicity.rs ├── test_utils.rs └── util.rs /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | # Automatically generated by fuzz/generate-files.sh 2 | name: Fuzz 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - 'test-ci/**' 9 | pull_request: 10 | 11 | jobs: 12 | fuzz: 13 | if: ${{ !github.event.act }} 14 | runs-on: ubuntu-20.04 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | fuzz_target: [ 19 | roundtrip_miniscript_str, 20 | roundtrip_miniscript_script, 21 | parse_descriptor, 22 | roundtrip_semantic, 23 | parse_descriptor_secret, 24 | roundtrip_descriptor, 25 | roundtrip_concrete, 26 | compile_descriptor, 27 | roundtrip_confidential, 28 | ] 29 | steps: 30 | - name: Install test dependencies 31 | 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 32 | - uses: actions/checkout@v2 33 | - uses: actions/cache@v2 34 | id: cache-fuzz 35 | with: 36 | path: | 37 | ~/.cargo/bin 38 | fuzz/target 39 | target 40 | key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: 1.65 44 | override: true 45 | profile: minimal 46 | - name: fuzz 47 | run: | 48 | if [[ "${{ matrix.fuzz_target }}" =~ ^bitcoin ]]; then 49 | export RUSTFLAGS='--cfg=hashes_fuzz --cfg=secp256k1_fuzz' 50 | fi 51 | echo "Using RUSTFLAGS $RUSTFLAGS" 52 | 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/rust.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: {} 6 | 7 | name: Continuous integration 8 | 9 | jobs: 10 | lint_fuzz_stable: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Crate 15 | uses: actions/checkout@v2 16 | - name: Install hongfuzz dependancies 17 | run: sudo apt-get update -y && sudo apt install -y build-essential binutils-dev libunwind-dev libblocksruntime-dev liblzma-dev 18 | - name: Checkout Toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: 1.63.0 23 | override: true 24 | - name: Running fuzzer 25 | env: 26 | DO_LINT: true 27 | run: ./contrib/test.sh 28 | 29 | Nightly: 30 | name: Nightly - Bench + Docs + Fmt 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout Crate 34 | uses: actions/checkout@v2 35 | - name: Checkout Toolchain 36 | uses: actions-rs/toolchain@v1 37 | with: 38 | profile: minimal 39 | toolchain: nightly 40 | override: true 41 | - name: Running benchmarks 42 | env: 43 | DO_BENCH: true 44 | run: ./contrib/test.sh 45 | - name: Building docs 46 | env: 47 | DO_DOCS: true 48 | run: ./contrib/test.sh 49 | 50 | Int-tests: 51 | name: Integration tests 52 | runs-on: ubuntu-latest 53 | steps: 54 | - name: Checkout Crate 55 | uses: actions/checkout@v2 56 | - name: Checkout Toolchain 57 | uses: actions-rs/toolchain@v1 58 | with: 59 | profile: minimal 60 | toolchain: stable 61 | override: true 62 | - name: Running integration tests 63 | env: 64 | DO_BITCOIND_TESTS: true 65 | run: ./contrib/test.sh 66 | 67 | Tests: 68 | name: Tests 69 | runs-on: ubuntu-latest 70 | strategy: 71 | fail-fast: false 72 | matrix: 73 | rust: [stable, beta, nightly, 1.63.0] 74 | steps: 75 | - name: Checkout Crate 76 | uses: actions/checkout@v2 77 | - name: Checkout Toolchain 78 | uses: actions-rs/toolchain@v1 79 | with: 80 | profile: minimal 81 | toolchain: ${{ matrix.rust }} 82 | override: true 83 | - name: Running cargo 84 | env: 85 | DO_FEATURE_MATRIX: true 86 | run: ./contrib/test.sh 87 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.4.0 - Oct 8, 2024 2 | 3 | - Use rust-bitcoin 0.32.0 and rust-elements 0.25.0 [#90](https://github.com/ElementsProject/elements-miniscript/pull/90) 4 | - Check input charset [#92](https://github.com/ElementsProject/elements-miniscript/pull/92) 5 | - Fix a bunch of clippy lints and get CI working again [#89](https://github.com/ElementsProject/elements-miniscript/pull/89) 6 | - avoid setting {BITCOIND,ELEMENTSD}\_EXE in setup [#88](https://github.com/ElementsProject/elements-miniscript/pull/88) 7 | - [Removed `to_string_no_chksum`](https://github.com/ElementsProject/elements-miniscript/pull/86). This method was poorly-named and broken. Use the alternate display `{:#}` formatter instead to format descriptors without a checksum. 8 | - Implement federation descriptor tweak with claiming script to match elements core getpeginaddress [#87](https://github.com/ElementsProject/elements-miniscript/pull/87) 9 | - elip151: multisig test vectors [#84](https://github.com/ElementsProject/elements-miniscript/pull/84) 10 | 11 | # 0.3.1 - May 10, 2024 12 | 13 | - [Fixed](https://github.com/ElementsProject/elements-miniscript/pull/81) ELIP-151 hash calculation 14 | 15 | # 0.3.0 - Jan 30, 2024 16 | 17 | - Add simplicity 18 | - Use rust-bitcoin 0.31.0 19 | - [elip150](https://github.com/ElementsProject/ELIPs/blob/main/elip-0150.mediawiki) 20 | - [elip151](https://github.com/ElementsProject/ELIPs/blob/main/elip-0151.mediawiki) 21 | 22 | # 0.2.0 - June 15, 2023 23 | 24 | - Still rapid iteration, very unstable. 25 | 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elements-miniscript" 3 | version = "0.4.0" 4 | authors = ["Andrew Poelstra , Sanket Kanjalkar "] 5 | repository = "https://github.com/ElementsProject/elements-miniscript" 6 | description = "Elements Miniscript: Miniscript, but for elements" 7 | license = "CC0-1.0" 8 | keywords = [ "crypto", "bitcoin", "miniscript", "script" ] 9 | readme = "README.md" 10 | homepage = "https://github.com/rust-bitcoin/rust-miniscript/" 11 | edition = "2018" 12 | 13 | [features] 14 | compiler = [] 15 | trace = [] 16 | 17 | serde = ["actual-serde", "bitcoin/serde", "elements/serde"] 18 | rand = ["bitcoin/rand"] 19 | base64 = ["bitcoin/base64", "elements/base64"] 20 | 21 | [dependencies] 22 | bitcoin = "0.32.0" 23 | elements = "0.25.0" 24 | bitcoin-miniscript = { package = "miniscript", version = "12.0" } 25 | simplicity = { package = "simplicity-lang", version = "0.3.0", optional = true } 26 | 27 | # Do NOT use this as a feature! Use the `serde` feature instead. 28 | actual-serde = { package = "serde", version = "1.0", optional = true } 29 | 30 | [dev-dependencies] 31 | serde_json = "1.0" 32 | actual-rand = { package = "rand", version = "0.8.4"} 33 | serde_test = "1.0.147" 34 | bitcoin = { version = "0.32.0", features = ["base64"] } 35 | secp256k1 = { version = "0.29.0", features = ["rand-std"] } 36 | actual-base64 = { package = "base64", version = "0.13.0" } 37 | 38 | [[example]] 39 | name = "htlc" 40 | required-features = ["compiler"] 41 | 42 | [[example]] 43 | name = "parse" 44 | 45 | [[example]] 46 | name = "sign_multisig" 47 | 48 | [[example]] 49 | name = "verify_tx" 50 | 51 | [[example]] 52 | name = "xpub_descriptors" 53 | 54 | [[example]] 55 | name = "taproot" 56 | required-features = ["compiler"] 57 | 58 | [[example]] 59 | name = "psbt_sign_finalize" 60 | required-features = ["base64"] 61 | 62 | [workspace] 63 | members = ["bitcoind-tests", "fuzz"] 64 | -------------------------------------------------------------------------------- /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 | ![Build](https://github.com/ElementsProject/elements-miniscript/workflows/Continuous%20integration/badge.svg) 2 | 3 | **Minimum Supported Rust Version:** 1.63.0 4 | 5 | *This crate uses "2018" edition 6 | 7 | # Elements Miniscript 8 | This library is a fork of [rust-miniscript](https://github.com/rust-bitcoin/rust-miniscript) for elements. 9 | 10 | 11 | ## High-Level Features 12 | 13 | This library supports 14 | 15 | * [Output descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) 16 | including embedded Miniscripts 17 | * Parsing and serializing descriptors to a human-readable string format 18 | * Compilation of abstract spending policies to Miniscript (enabled by the 19 | `compiler` flag) 20 | * Semantic analysis of Miniscripts and spending policies, with user-defined 21 | public key types 22 | * Encoding and decoding Miniscript as Bitcoin Script, given key types that 23 | are convertible to `bitcoin::PublicKey` 24 | * Determining satisfiability, and optimal witnesses, for a given descriptor; 25 | completing an unsigned `elements::TxIn` with appropriate data 26 | * Determining the specific keys, hash preimages and timelocks used to spend 27 | coins in a given Bitcoin transaction 28 | 29 | More information can be found in [the documentation](https://docs.rs/elements-miniscript) 30 | or in [the `examples/` directory](https://github.com/ElementsProject/elements-miniscript/tree/master/examples) 31 | 32 | ## Building 33 | 34 | The cargo feature `std` is enabled by default. At least one of the features `std` or `no-std` or both must be enabled. 35 | 36 | Enabling the `no-std` feature does not disable `std`. To disable the `std` feature you must disable default features. The `no-std` feature only enables additional features required for this crate to be usable without `std`. Both can be enabled without conflict. 37 | 38 | ## Benchmarking 39 | 40 | To run the benchmarks run `RUSTFLAGS=--cfg=miniscript_bench cargo +nightly bench --all-features`. 41 | 42 | ## Minimum Supported Rust Version (MSRV) 43 | This library should always compile with any combination of features on **Rust 1.63.0**. 44 | 45 | 46 | Some dependencies do not play nicely with our MSRV, if you are running the tests 47 | you may need to pin as follows: 48 | 49 | ``` 50 | cargo update -p byteorder --precise 1.4.3 51 | ``` 52 | 53 | Note this list could sometimes be not exhaustive because not enforced by CI. 54 | If you have any issues check the script executed in CI: `contrib/test.sh` 55 | 56 | ## Contributing 57 | Contributions are generally welcome. If you intend to make larger changes please 58 | discuss them in an issue before PRing them to avoid duplicate work and 59 | architectural mismatches. If you have any questions or ideas you want to discuss 60 | please join us in 61 | [##miniscript](https://web.libera.chat/?channels=##miniscript) on Libera. 62 | 63 | 64 | ## Release Notes 65 | 66 | See [CHANGELOG.md](CHANGELOG.md). 67 | 68 | 69 | ## Licensing 70 | 71 | The code in this project is licensed under the [Creative Commons CC0 1.0 72 | Universal license](LICENSE). We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX 73 | IDs](https://spdx.dev/ids/). 74 | -------------------------------------------------------------------------------- /bitcoind-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoind-tests" 3 | version = "0.1.0" 4 | authors = ["sanket1729 "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | elements-miniscript = { path = "../" } 11 | elementsd = { version = "0.9.0" } 12 | actual-rand = { package = "rand", version = "0.8.4" } 13 | secp256k1 = { version = "0.29.0", features = ["rand-std"] } 14 | -------------------------------------------------------------------------------- /bitcoind-tests/bin/bitcoind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementsProject/elements-miniscript/ffc3ea01f2fc8a127d1130b3b2cd0ad0c287272c/bitcoind-tests/bin/bitcoind -------------------------------------------------------------------------------- /bitcoind-tests/bin/elementsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementsProject/elements-miniscript/ffc3ea01f2fc8a127d1130b3b2cd0ad0c287272c/bitcoind-tests/bin/elementsd -------------------------------------------------------------------------------- /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 | pub extern crate elements_miniscript; 2 | 3 | use std::str::FromStr; 4 | 5 | use elements::encode::{deserialize, serialize_hex}; 6 | use elements::hex::FromHex; 7 | use elements::BlockHash; 8 | pub use elements_miniscript as miniscript; 9 | use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; 10 | use elementsd::bitcoind::bitcoincore_rpc::RpcApi; 11 | use elementsd::bitcoind::{self, BitcoinD}; 12 | use elementsd::ElementsD; 13 | use miniscript::elements; 14 | 15 | pub mod test_util; 16 | 17 | // We are not using pegins right now, but it might be required in case in future 18 | // if we extend the tests to check pegins etc. 19 | pub fn setup(validate_pegin: bool) -> (ElementsD, Option, elements::BlockHash) { 20 | let mut bitcoind = None; 21 | if validate_pegin { 22 | let bitcoind_exe = bitcoind::exe_path().unwrap(); 23 | let bitcoind_conf = bitcoind::Conf::default(); 24 | bitcoind = Some(bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap()); 25 | } 26 | 27 | let mut conf = elementsd::Conf::new(bitcoind.as_ref()); 28 | 29 | // HACK: Upstream has issued only 21 million sats intially, but our hard coded tests 30 | // consume more coins. In order to avoid further conflicts, mutate the default arg here. 31 | 32 | let arg_pos = conf 33 | .0 34 | .args 35 | .iter() 36 | .position(|x| x.starts_with("-initialfreecoins=")); 37 | 38 | match arg_pos { 39 | Some(i) => conf.0.args[i] = "-initialfreecoins=210000000000", 40 | None => conf.0.args.push("-initialfreecoins=210000000000"), 41 | }; 42 | 43 | let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); 44 | 45 | let create = elementsd.call("createwallet", &["wallet".into()]); 46 | assert_eq!(create.get("name").unwrap(), "wallet"); 47 | 48 | let rescan = elementsd.call("rescanblockchain", &[]); 49 | assert_eq!(rescan.get("stop_height").unwrap().as_u64().unwrap(), 0); 50 | 51 | let balances = elementsd.call("getbalances", &[]); 52 | let mine = balances.get("mine").unwrap(); 53 | let trusted = mine.get("trusted").unwrap(); 54 | assert_eq!(trusted.get("bitcoin").unwrap(), 2100.0); 55 | 56 | let genesis_str = elementsd.call("getblockhash", &[0u32.into()]); 57 | let genesis_str = genesis_str.as_str().unwrap(); 58 | let genesis_hash = BlockHash::from_str(genesis_str).unwrap(); 59 | 60 | (elementsd, bitcoind, genesis_hash) 61 | } 62 | // Upstream all common methods later 63 | pub trait Call { 64 | fn call(&self, cmd: &str, args: &[Value]) -> Value; 65 | fn get_new_address(&self) -> elements::Address; 66 | fn send_to_address(&self, addr: &elements::Address, amt: &str) -> elements::Txid; 67 | fn get_transaction(&self, txid: &elements::Txid) -> elements::Transaction; 68 | fn test_mempool_accept(&self, hex: &elements::Transaction) -> bool; 69 | fn send_raw_transaction(&self, hex: &elements::Transaction) -> elements::Txid; 70 | fn generate(&self, blocks: u32); 71 | } 72 | 73 | impl Call for ElementsD { 74 | fn call(&self, cmd: &str, args: &[Value]) -> Value { 75 | self.client().call::(cmd, args).unwrap() 76 | } 77 | 78 | fn get_new_address(&self) -> elements::Address { 79 | let addr_str = self 80 | .call("getnewaddress", &[]) 81 | .as_str() 82 | .unwrap() 83 | .to_string(); 84 | 85 | elements::Address::from_str(&addr_str).unwrap() 86 | } 87 | 88 | fn get_transaction(&self, txid: &elements::Txid) -> elements::Transaction { 89 | let tx_hex = self.call("gettransaction", &[txid.to_string().into()])["hex"] 90 | .as_str() 91 | .unwrap() 92 | .to_string(); 93 | 94 | let tx_bytes = Vec::::from_hex(&tx_hex).unwrap(); 95 | deserialize(&tx_bytes).unwrap() 96 | } 97 | 98 | fn send_to_address(&self, addr: &elements::Address, amt: &str) -> elements::Txid { 99 | let tx_id = self 100 | .call("sendtoaddress", &[addr.to_string().into(), amt.into()]) 101 | .as_str() 102 | .unwrap() 103 | .to_string(); 104 | elements::Txid::from_str(&tx_id).unwrap() 105 | } 106 | 107 | fn send_raw_transaction(&self, tx: &elements::Transaction) -> elements::Txid { 108 | let tx_id = self 109 | .call("sendrawtransaction", &[serialize_hex(tx).into()]) 110 | .as_str() 111 | .unwrap() 112 | .to_string(); 113 | 114 | elements::Txid::from_str(&tx_id).unwrap() 115 | } 116 | 117 | fn generate(&self, blocks: u32) { 118 | let address = self.get_new_address(); 119 | let _value = self.call( 120 | "generatetoaddress", 121 | &[blocks.into(), address.to_string().into()], 122 | ); 123 | } 124 | 125 | fn test_mempool_accept(&self, tx: &elements::Transaction) -> bool { 126 | let result = self.call("testmempoolaccept", &[json!([serialize_hex(tx)])]); 127 | let allowed = result.get(0).unwrap().get("allowed"); 128 | allowed.unwrap().as_bool().unwrap() 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /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::fs::File; 8 | use std::io::{self, BufRead}; 9 | use std::path::Path; 10 | 11 | use bitcoin::hashes::{sha256d, Hash}; 12 | use bitcoin::secp256k1::{self, Secp256k1}; 13 | use elements::pset::PartiallySignedTransaction as Psbt; 14 | use elements::{ 15 | confidential, pset as psbt, secp256k1_zkp, AssetIssuance, LockTime, OutPoint, Script, Sequence, 16 | TxIn, TxInWitness, TxOut, TxOutWitness, Txid, 17 | }; 18 | use elements_miniscript as miniscript; 19 | use elementsd::ElementsD; 20 | use miniscript::psbt::PsbtExt; 21 | use miniscript::{bitcoin, elements, elementssig_to_rawsig, Descriptor}; 22 | 23 | mod setup; 24 | use setup::test_util::{self, PubData, TestData, PARAMS}; 25 | use setup::Call; 26 | 27 | // parse ~30 miniscripts from file 28 | pub(crate) fn parse_miniscripts( 29 | secp: &Secp256k1, 30 | pubdata: &PubData, 31 | ) -> Vec> { 32 | // File must exist in current path before this produces output 33 | let mut desc_vec = vec![]; 34 | // Consumes the iterator, returns an (Optional) String 35 | for line in read_lines("tests/data/random_ms.txt") { 36 | let ms = test_util::parse_insane_ms(&line.unwrap(), pubdata); 37 | let wsh = Descriptor::new_wsh(ms).unwrap(); 38 | desc_vec.push(wsh.derived_descriptor(secp, 0).unwrap()); 39 | } 40 | desc_vec 41 | } 42 | 43 | // The output is wrapped in a Result to allow matching on errors 44 | // Returns an Iterator to the Reader of the lines of the file. 45 | fn read_lines

(filename: P) -> io::Lines> 46 | where 47 | P: AsRef, 48 | { 49 | let file = File::open(filename).expect("File not found"); 50 | io::BufReader::new(file).lines() 51 | } 52 | 53 | // Find the Outpoint by value. 54 | // Ideally, we should find by scriptPubkey, but this 55 | // works for temp test case 56 | fn get_vout(cl: &ElementsD, txid: Txid, value: u64) -> (OutPoint, TxOut) { 57 | let tx = cl.get_transaction(&txid); 58 | for (i, txout) in tx.output.into_iter().enumerate() { 59 | if txout.value == confidential::Value::Explicit(value) { 60 | return (OutPoint::new(txid, i as u32), txout); 61 | } 62 | } 63 | unreachable!("Only call get vout on functions which have the expected outpoint"); 64 | } 65 | 66 | pub fn test_from_cpp_ms(cl: &ElementsD, testdata: &TestData) { 67 | let secp = secp256k1::Secp256k1::new(); 68 | let desc_vec = parse_miniscripts(&secp, &testdata.pubdata); 69 | let sks = &testdata.secretdata.sks; 70 | let pks = &testdata.pubdata.pks; 71 | // Generate some blocks 72 | cl.generate(500); 73 | 74 | // Next send some btc to each address corresponding to the miniscript 75 | // Create a hard- 76 | let mut txids = vec![]; 77 | for wsh in desc_vec.iter() { 78 | let txid = cl.send_to_address( 79 | &wsh.address(&PARAMS).unwrap(), // This is unblinded address 80 | "1", 81 | ); 82 | cl.generate(1); 83 | txids.push(txid); 84 | } 85 | // Wait for the funds to mature. 86 | cl.generate(50); 87 | // Create a PSBT for each transaction. 88 | // Spend one input and spend one output for simplicity. 89 | let mut psbts = vec![]; 90 | for (desc, txid) in desc_vec.iter().zip(txids) { 91 | let mut psbt = Psbt::new_v2(); 92 | psbt.global.tx_data.fallback_locktime = 93 | Some(LockTime::from_time(1_603_866_330).expect("valid timestamp")); // 10/28/2020 @ 6:25am (UTC) 94 | let (outpoint, witness_utxo) = get_vout(cl, txid, 100_000_000); 95 | let txin = TxIn { 96 | previous_output: outpoint, 97 | is_pegin: false, 98 | script_sig: Script::new(), 99 | sequence: Sequence::from_height(49), // We waited 50 blocks, keep 49 for safety 100 | asset_issuance: AssetIssuance::default(), 101 | witness: TxInWitness::default(), 102 | }; 103 | psbt.add_input(psbt::Input::from_txin(txin)); 104 | // Get a new script pubkey from the node so that 105 | // the node wallet tracks the receiving transaction 106 | // and we can check it by gettransaction RPC. 107 | let addr = cl.get_new_address(); 108 | let out = TxOut { 109 | value: confidential::Value::Explicit(99_999_000), 110 | script_pubkey: addr.script_pubkey(), 111 | asset: witness_utxo.asset, 112 | nonce: confidential::Nonce::Null, 113 | witness: TxOutWitness::default(), 114 | }; 115 | psbt.add_output(psbt::Output::from_txout(out)); 116 | // ELEMENTS: Add fee output 117 | let fee_out = TxOut::new_fee(1_000, witness_utxo.asset.explicit().unwrap()); 118 | psbt.add_output(psbt::Output::from_txout(fee_out)); 119 | 120 | psbt.inputs_mut()[0].witness_utxo = Some(witness_utxo); 121 | psbt.inputs_mut()[0].witness_script = Some(desc.explicit_script().unwrap()); 122 | psbts.push(psbt); 123 | } 124 | 125 | let mut spend_txids = vec![]; 126 | // Sign the transactions with all keys 127 | // AKA the signer role of psbt 128 | for i in 0..psbts.len() { 129 | let ms = if let Descriptor::Wsh(wsh) = &desc_vec[i] { 130 | match wsh.as_inner() { 131 | miniscript::descriptor::WshInner::Ms(ms) => ms, 132 | _ => unreachable!(), 133 | } 134 | } else { 135 | unreachable!("Only Wsh descriptors are supported"); 136 | }; 137 | 138 | let sks_reqd: Vec<_> = ms 139 | .iter_pk() 140 | .map(|pk| sks[pks.iter().position(|&x| x == pk).unwrap()]) 141 | .collect(); 142 | // Get the required sighash message 143 | let amt = confidential::Value::Explicit(100_000_000); 144 | let unsigned_tx = psbts[i].extract_tx().unwrap(); 145 | let mut sighash_cache = elements::sighash::SighashCache::new(&unsigned_tx); 146 | let sighash_ty = elements::EcdsaSighashType::All; 147 | let sighash = sighash_cache.segwitv0_sighash(0, &ms.encode(), amt, sighash_ty); 148 | 149 | // requires both signing and verification because we check the tx 150 | // after we psbt extract it 151 | let msg = secp256k1_zkp::Message::from_digest_slice(&sighash[..]).unwrap(); 152 | 153 | // Finally construct the signature and add to psbt 154 | for sk in sks_reqd { 155 | let sig = secp.sign_ecdsa(&msg, &sk); 156 | let ser_sig = elementssig_to_rawsig(&(sig, sighash_ty)); 157 | let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; 158 | psbts[i].inputs_mut()[0].partial_sigs.insert(pk, ser_sig); 159 | } 160 | // Add the hash preimages to the psbt 161 | psbts[i].inputs_mut()[0].sha256_preimages.insert( 162 | testdata.pubdata.sha256, 163 | testdata.secretdata.sha256_pre.to_vec(), 164 | ); 165 | psbts[i].inputs_mut()[0].hash256_preimages.insert( 166 | sha256d::Hash::from_byte_array(testdata.pubdata.hash256.to_byte_array()), 167 | testdata.secretdata.hash256_pre.to_vec(), 168 | ); 169 | println!("{}", ms); 170 | psbts[i].inputs_mut()[0].hash160_preimages.insert( 171 | testdata.pubdata.hash160, 172 | testdata.secretdata.hash160_pre.to_vec(), 173 | ); 174 | psbts[i].inputs_mut()[0].ripemd160_preimages.insert( 175 | testdata.pubdata.ripemd160, 176 | testdata.secretdata.ripemd160_pre.to_vec(), 177 | ); 178 | // Finalize the transaction using psbt 179 | // Let miniscript do it's magic! 180 | if let Err(e) = psbts[i].finalize_mall_mut(&secp, elements::BlockHash::all_zeros()) { 181 | // All miniscripts should satisfy 182 | panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i); 183 | } else { 184 | // default genesis hash 185 | let tx = psbts[i] 186 | .extract(&secp, elements::BlockHash::all_zeros()) 187 | .unwrap(); 188 | 189 | // Send the transactions to bitcoin node for mining. 190 | // Regtest mode has standardness checks 191 | // Check whether the node accepts the transactions 192 | let txid = cl.send_raw_transaction(&tx); 193 | spend_txids.push(txid); 194 | } 195 | } 196 | // Finally mine the blocks and await confirmations 197 | cl.generate(10); 198 | // Get the required transactions from the node mined in the blocks. 199 | for txid in spend_txids { 200 | // Check whether the transaction is mined in blocks 201 | // Assert that the confirmations are > 0. 202 | let num_conf = cl.call("gettransaction", &[txid.to_string().into()])["confirmations"] 203 | .as_u64() 204 | .unwrap(); 205 | assert!(num_conf > 0); 206 | } 207 | } 208 | 209 | #[test] 210 | fn test_setup() { 211 | setup::setup(false); 212 | setup::setup(true); 213 | } 214 | 215 | #[test] 216 | fn tests_from_cpp() { 217 | let (cl, _, genesis_hash) = &setup::setup(false); 218 | let testdata = TestData::new_fixed_data(50, *genesis_hash); 219 | test_from_cpp_ms(cl, &testdata); 220 | } 221 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.63.0" 2 | -------------------------------------------------------------------------------- /contrib/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | FEATURES="compiler serde rand base64 simplicity" 6 | 7 | cargo --version 8 | rustc --version 9 | 10 | # Pin dependencies required to build with Rust 1.63 11 | if cargo --version | grep "1\.63"; then 12 | cargo update -p regex --precise 1.8.4 13 | fi 14 | 15 | # Format if told to 16 | if [ "$DO_FMT" = true ] 17 | then 18 | rustup component add rustfmt 19 | cargo fmt -- --check 20 | fi 21 | 22 | # Test bitcoind integration tests if told to (this only works with the stable toolchain) 23 | if [ "$DO_BITCOIND_TESTS" = true ]; then 24 | 25 | BITCOIND_EXE_DEFAULT="$(git rev-parse --show-toplevel)/bitcoind-tests/bin/bitcoind" 26 | ELEMENTSD_EXE_DEFAULT="$(git rev-parse --show-toplevel)/bitcoind-tests/bin/elementsd" 27 | 28 | cd bitcoind-tests 29 | 30 | BITCOIND_EXE=${BITCOIND_EXE:=${BITCOIND_EXE_DEFAULT}} \ 31 | ELEMENTSD_EXE=${ELEMENTSD_EXE:=${ELEMENTSD_EXE_DEFAULT}} \ 32 | cargo test --verbose 33 | 34 | # Exit integration tests, do not run other tests. 35 | exit 0 36 | fi 37 | 38 | # Defaults / sanity checks 39 | cargo test 40 | 41 | if [ "$DO_FEATURE_MATRIX" = true ] 42 | then 43 | # All features 44 | cargo test --features="$FEATURES" 45 | 46 | # Single features 47 | for feature in ${FEATURES} 48 | do 49 | cargo test --features="$feature" 50 | done 51 | 52 | # Run all the examples 53 | cargo build --examples 54 | cargo run --example htlc --features=compiler 55 | cargo run --example parse 56 | cargo run --example sign_multisig 57 | cargo run --example verify_tx > /dev/null 58 | cargo run --example xpub_descriptors 59 | cargo run --example taproot --features=compiler 60 | cargo run --example psbt_sign_finalize --features=base64 61 | fi 62 | 63 | # Bench if told to (this only works with the nightly toolchain) 64 | if [ "$DO_BENCH" = true ] 65 | then 66 | RUSTFLAGS=--cfg=miniscript_bench cargo bench --features="compiler" 67 | fi 68 | 69 | # Build the docs if told to (this only works with the nightly toolchain) 70 | if [ "$DO_DOCS" = true ]; then 71 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly rustdoc --features="$FEATURES" -- -D rustdoc::broken-intra-doc-links 72 | fi 73 | 74 | exit 0 75 | -------------------------------------------------------------------------------- /doc/ReasoningAboutMultipartyMiniscript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementsProject/elements-miniscript/ffc3ea01f2fc8a127d1130b3b2cd0ad0c287272c/doc/ReasoningAboutMultipartyMiniscript.pdf -------------------------------------------------------------------------------- /doc/Tr Compiler.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElementsProject/elements-miniscript/ffc3ea01f2fc8a127d1130b3b2cd0ad0c287272c/doc/Tr Compiler.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](doc/taproot_compiler.md). 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. -------------------------------------------------------------------------------- /doc/extension_spec.md: -------------------------------------------------------------------------------- 1 | # Miniscript Extensions: 2 | 3 | Extensions allow users to extend miniscript to have new leaf nodes. This document lists 4 | extensions implemented for elements-miniscript with the tapscript opcodes. Users can 5 | also implement custom extensions using [`Extension`] trait. 6 | 7 | # Index expressions (`IdxExpr`) 8 | - Pushes a single CScriptNum on stack top. This is used to represent the index of the input or output. `IndexExpr` must 9 | compute values between [-2^31, 2^31 - 1]. When `IndexExpr` is finally used as an index in some parent fragment, additionally 10 | it's value must fall within bounds. For example, `inp_spk(IdxExpr_i)`, `IdxExpr_i` must be between `0`(inclusive) and 11 | `num_inputs`(exclusive) 12 | 13 | Name | Script 14 | --- | --- 15 | curr_idx | `PUSHCURRENTINPUTINDEX` 16 | `i` `` | `i` (`i` as `CScriptNum`) 17 | idx_add(x,y) | `[X] [Y] ADD` 18 | idx_sub(x,y) | `[X] [Y] SUB` 19 | idx_mul(x,y) | `[X] SCIPTNUMTOLE64 [Y] OP_SCIPTNUMTOLE64 MUL64 <1> EQUALVERIFY LE64TOSCIPTNUM` 20 | idx_div(x,y) | `[X] SCIPTNUMTOLE64 [Y] OP_SCIPTNUMTOLE64 DIV64 <1> EQUALVERIFY NIP LE64TOSCIPTNUM` 21 | 22 | # Value Arithmetic extensions (`NumExpr`) 23 | 24 | - Pushes single singed 64 bit LE number on stack top. Since these expressions push a 8 byte number, it does not directly 25 | fit in the miniscript model. These are used in fragments in one of the comparison fragment listed in the next section. 26 | - All of introspection opcodes explicitly assert the amount is explicit. 27 | - This will abort when 28 | - Any of operations are on confidential amounts. The Null case is automatically converted to explicit zero. 29 | - Supplied index is out of bounds. 30 | - Any of the operations overflow. Refer to tapscript [opcodes](https://github.com/ElementsProject/elements/blob/master/doc/tapscript_opcodes.md) spec for overflow specification 31 | 32 | Name | Script 33 | --- | --- 34 | `value` `` | `8-byte-LE-push of ` 35 | curr_inp_v | `INSPECTCURRENTINPUTINDEX INPSECTINPUTVALUE <1> EQUALVERIFY` 36 | inp_v(IdxExpr_i) | `[IdxExpr_i] INPSECTINPUTVALUE <1> EQUALVERIFY` 37 | out_v(IdxExpr_i) | `[IdxExpr_i] INPSECTOUTPUTVALUE <1> EQUALVERIFY` 38 | inp_issue_v(IdxExpr_i) | `[IdxExpr_i] OP_INSPECTINPUTISSUANCE DROP DROP <1> EQUALVERIFY NIP NIP` 39 | inp_reissue_v(IdxExpr_i)| `[IdxExpr_i] OP_INSPECTINPUTISSUANCE DROP DROP DROP DROP <1> EQUALVERIFY` 40 | bitinv(x) | `[X] INVERT` 41 | neg(x) | `[X] NEG64 <1> EQUALVERIFY` 42 | add(x,y) | `[X] [Y] ADD64 <1> EQUALVERIFY` 43 | sub(x,y) | `[X] [Y] SUB64 <1> EQUALVERIFY` 44 | mul(x,y) | `[X] [Y] MUL64 <1> EQUALVERIFY` 45 | div(x,y) | `[X] [Y] DIV64 <1> EQUALVERIFY NIP` 46 | mod(x,y) | `[X] [Y] DIV64 <1> EQUALVERIFY DROP` 47 | bitand(x,y) | `[X] [Y] AND` 48 | bitor(x,y) | `[X] [Y] OR (cannot fail)` 49 | bitxor(x,y) | `[X] [Y] XOR (cannot fail)` 50 | price_oracle1(K,T) | `2DUP TOALTSTACK OP_GREATERTHANEQ VERIFY CAT SHA256 CHECKSIGFROMSTACKVERIFY OP_FROMATLSTACK` 51 | price_oracle1_w(K,T) | `TOALTSTACK 2DUP TOALTSTACK OP_GREATERTHANEQ VERIFY CAT SHA256 CHECKSIGFROMSTACKVERIFY OP_FROMATLSTACK FROMALTSTACK SWAP` 52 | 53 | - The division operation pushes the quotient(a//b) such that the remainder a%b (must be non-negative and less than |b|). 54 | - neg(a) returns -a, whereas bitinv(a) returns ~a. 55 | - `price_oracle1(K,T)` pushes a 64 bit LE integer(price) of signed with key K. It checks whether the price is signed 56 | with at a timestamp greater than T. Roughly spea 57 | - K can be any `KEY` expression in descriptor format, but it not allowed to be uncompressed key. 58 | - T is a 64 byte LE UXIX timestamp. 59 | - `1` is the version of the oracle. There can be multiple versions of the 60 | oracle with different fragments. `price_oracle1` creates a schnorr signature with given key `K` on a message that is 61 | computed as: `sha256(T1||K)` 62 | - The fragment consumes three inputs from stack top: [`signature`, `timestamp`, `price`] where `price` is the 63 | stack top. 64 | - `price_oracle1_w` must be used when the price_oracle is not the first leaf fragment. When price_oracle is the first 65 | argument in fragment, use `price_oracle1`. For example, 66 | - `num64_eq(price_oracle1_(K,T),10))` is valid, but `num64_eq(10,price_oracle1_(K,T))` is not. 67 | - `num64_eq(price_oracle1_w(K,T),10))` is not valid, but `num64_eq(10,price_oracle1_w(K,T))` is also valid. 68 | - `num64_eq(add(10,price_oracle1(K,T)),price_oracle1_w(K,T))` is not valid because `10` is the first leaf terminal. 69 | - `num64_eq(add(price_oracle1(K,T),10),price_oracle1_w(K,T))` is valid because `price_oracle1` is the first leaf terminal. 70 | ## Comparison extensions 71 | 72 | As mentioned earlier, `NumExpr` directly does not fit in the miniscript model as it pushes a 8 byte computation result. 73 | To use these with miniscript fragments, we can use them inside comparison extensions. These comparison are of type `Bzdu`. 74 | 75 | Name | Script 76 | --- | --- 77 | num64_eq(NumExpr_X,NumExpr_Y) | `[NumExpr_X] [NumExpr_Y] EQUAL` 78 | num64_lt(NumExpr_X,NumExpr_Y) | `[NumExpr_X] [NumExpr_Y] LESSTHAN64` 79 | num64_gt(NumExpr_X,NumExpr_Y) | `[NumExpr_X] [NumExpr_Y] GREATERTHAN64` 80 | num64_leq(NumExpr_X,NumExpr_Y) | `[NumExpr_X] [NumExpr_Y] LESSTHANOREQUAL64` 81 | num64_geq(NumExpr_X,NumExpr_Y) | `[NumExpr_X] [NumExpr_Y] GREATERTHANOREQUAL64` 82 | 83 | - For example, `num64_eq(inp_v(1),mul(curr_inp_v,20))` represents second input value is the multiplication of 84 | current input value and fourth output value. This would abort if any of the values are confidential. 85 | 86 | ### Tx Value introspection 87 | 88 | ### AssetExpr 89 | 90 | - pushes a 32 byte asset + 1 byte prefix on stack top. These operations also support confidential assets. 91 | - This will abort when 92 | - Supplied index is out of bounds. 93 | 94 | Name | Script 95 | --- | --- 96 | `asset`(33 byte hex) | `[32-byte comm] [1 byte pref]` of this asset 97 | curr_inp_asset | `INSPECTCURRENTINPUTINDEX INPSECTINPUTASSET` 98 | inp_asset(IdxExpr_i) | `[IdxExpr_i] INPSECTINPUTASSET` 99 | out_asset(IdxExpr_i) | `[IdxExpr_i] INPSECTOUTPUTASSET` 100 | 101 | ### ValueExpr 102 | 103 | - pushes a 32 byte value(8-byte-LE value if explicit) + 1 byte prefix on stack top. These operations also support confidential values. 104 | - This will abort when 105 | - Supplied index is out of bounds. 106 | 107 | Name | Script 108 | --- | --- 109 | `value`(33/9 byte hex) | `[32-byte comm/8 byte LE] [1 byte pref]` of this Value 110 | curr_inp_value | `INSPECTCURRENTINPUTINDEX INPSECTINPUTVALUE` 111 | inp_value(IdxExpr_i) | `[IdxExpr_i] INPSECTINPUTVALUE` 112 | out_value(IdxExpr_i) | `[IdxExpr_i] INPSECTOUTPUTVALUE` 113 | 114 | ### SpkExpr: Script PubKey Expression 115 | 116 | - Pushes a witness program + 1 byte witness version on stack top. 117 | - If the script pubkey is not a witness program. Push a sha256 hash of the script pubkey followed by -1 witness version 118 | - This will abort when 119 | - Supplied index is out of bounds. 120 | 121 | Name | Script 122 | --- | --- 123 | `spk`(script_hex) | `[program] [witness version]` of this spk (` <-1>`) for legacy 124 | curr_inp_spk | `INSPECTCURRENTINPUTINDEX INPSECTINPUTSCRIPTPUBKEY` 125 | inp_spk(IdxExpr_i) | `[IdxExpr_i] INPSECTINPUTSCRIPTPUBKEY` 126 | out_spk(IdxExpr_i) | `[IdxExpr_i] INPSECTOUTPUTASSETSCRIPTPUBKEY` 127 | 128 | ## Introspection Operations 129 | 130 | - `ValueExpr`, `AssetExpr` and `SpkExpr` do not fit in to the miniscript model. To use these 131 | in miniscript, we can use the below defined introspection operations. These are of type `Bzdu` 132 | - Reasoning the safety of covenants using introspection is not possible for miniscript to do as 133 | from point of view of miniscript these are anyone can spend without any signatures. However, in 134 | practice these are usually secured via cross input transactional logic beyond the current executing script. 135 | 136 | Name | Script 137 | --- | --- 138 | is_exp_asset(AssetExpr_X) | `[AssetExpr_X] <1> EQUAL NIP` 139 | is_exp_value(ValueExpr_X) | `[ValueExpr_X] <1> EQUAL NIP` 140 | asset_eq(AssetExpr_X,AssetExpr_Y) | `[AssetExpr_X] TOALTSTACK [AssetExpr_Y] FROMALTSTACK EQUAL TOALTSTACK EQUAL FROMALTSTACK BOOLAND` 141 | value_eq(ValueExpr_X,ValueExpr_Y) | `[ValueExpr_X] TOALTSTACK [ValueExpr_Y] FROMALTSTACK EQUAL TOALTSTACK EQUAL FROMALTSTACK BOOLAND` 142 | spk_eq(SpkExpr_X,SpkExpr_Y) | `[SpkExpr_X] TOALTSTACK [SpkExpr_Y] FROMALTSTACK EQUAL TOALTSTACK EQUAL FROMALTSTACK BOOLAND` 143 | curr_idx_eq(i) | `i PUSHCURRENTINPUTINDEX EQUAL` 144 | idx_eq(IdxExpr_i, IdxExpr_j) | `[IdxExpr_i] PUSHCURRENTINPUTINDEX EQUAL` 145 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | 12 | use alloc_cortex_m::CortexMHeap; 13 | 14 | use core::str::FromStr; 15 | 16 | use cortex_m::asm; 17 | use cortex_m_rt::entry; 18 | use cortex_m_semihosting::{debug, hprintln}; 19 | 20 | // this is the allocator the application will use 21 | #[global_allocator] 22 | static ALLOCATOR: CortexMHeap = CortexMHeap::empty(); 23 | 24 | const HEAP_SIZE: usize = 1024 * 256; // 256 KB 25 | 26 | #[entry] 27 | fn main() -> ! { 28 | hprintln!("heap size {}", HEAP_SIZE).unwrap(); 29 | 30 | unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) } 31 | 32 | // begin miniscript test 33 | let descriptor = "sh(wsh(or_d(\ 34 | c:pk_k(020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261),\ 35 | c:pk_k(0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352)\ 36 | )))"; 37 | hprintln!("descriptor {}", descriptor).unwrap(); 38 | let desc = 39 | miniscript::Descriptor::::from_str(descriptor).unwrap(); 40 | 41 | // Derive the P2SH address 42 | let p2sh_addr = desc 43 | .address(miniscript::bitcoin::Network::Bitcoin) 44 | .unwrap() 45 | .to_string(); 46 | hprintln!("p2sh address {}", p2sh_addr).unwrap(); 47 | assert_eq!(p2sh_addr, "3CJxbQBfWAe1ZkKiGQNEYrioV73ZwvBWns"); 48 | 49 | // Check whether the descriptor is safe 50 | // This checks whether all spend paths are accessible in bitcoin network. 51 | // It maybe possible that some of the spend require more than 100 elements in Wsh scripts 52 | // Or they contain a combination of timelock and heightlock. 53 | assert!(desc.sanity_check().is_ok()); 54 | 55 | // Estimate the satisfaction cost 56 | assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288); 57 | // end miniscript test 58 | 59 | // exit QEMU 60 | // NOTE do not run this on hardware; it can corrupt OpenOCD state 61 | debug::exit(debug::EXIT_SUCCESS); 62 | 63 | loop {} 64 | } 65 | 66 | // define what happens in an Out Of Memory (OOM) condition 67 | #[alloc_error_handler] 68 | fn alloc_error(_layout: Layout) -> ! { 69 | hprintln!("alloc error").unwrap(); 70 | debug::exit(debug::EXIT_FAILURE); 71 | asm::bkpt(); 72 | 73 | loop {} 74 | } 75 | 76 | #[inline(never)] 77 | #[panic_handler] 78 | fn panic(info: &PanicInfo) -> ! { 79 | hprintln!("panic {:?}", info.message()).unwrap(); 80 | debug::exit(debug::EXIT_FAILURE); 81 | loop {} 82 | } 83 | -------------------------------------------------------------------------------- /examples/htlc.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2019 by 3 | // Thomas Eizinger 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Example: Create an HTLC with miniscript using the policy compiler 16 | 17 | extern crate elements_miniscript as miniscript; 18 | 19 | use std::str::FromStr; 20 | 21 | use crate::miniscript::descriptor::Wsh; 22 | use crate::miniscript::policy::{Concrete, Liftable}; 23 | 24 | fn main() { 25 | // HTLC policy with 10:1 odds for happy (co-operative) case compared to uncooperative case. 26 | let htlc_policy = Concrete::::from_str(&format!("or(10@and(sha256({secret_hash}),pk({redeem_identity})),1@and(older({expiry}),pk({refund_identity})))", 27 | secret_hash = "1111111111111111111111111111111111111111111111111111111111111111", 28 | redeem_identity = "022222222222222222222222222222222222222222222222222222222222222222", 29 | refund_identity = "020202020202020202020202020202020202020202020202020202020202020202", 30 | expiry = "4444" 31 | )).unwrap(); 32 | 33 | let htlc_descriptor = Wsh::new( 34 | htlc_policy 35 | .compile() 36 | .expect("Policy compilation only fails on resource limits or mixed timelocks"), 37 | ) 38 | .expect("Resource limits"); 39 | 40 | // Check whether the descriptor is safe. This checks whether all spend paths are accessible in 41 | // the Bitcoin network. It may be possible that some of the spend paths require more than 100 42 | // elements in Wsh scripts or they contain a combination of timelock and heightlock. 43 | assert!(htlc_descriptor.sanity_check().is_ok()); 44 | assert_eq!( 45 | format!("{}", htlc_descriptor), 46 | "elwsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(020202020202020202020202020202020202020202020202020202020202020202),older(4444))))#mm63458x" 47 | ); 48 | 49 | // Lift the descriptor into an abstract policy. 50 | assert_eq!( 51 | format!("{}", htlc_descriptor.lift().unwrap()), 52 | "or(and(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111)),and(pk(020202020202020202020202020202020202020202020202020202020202020202),older(4444)))" 53 | ); 54 | 55 | // Get the scriptPpubkey for this Wsh descriptor. 56 | assert_eq!( 57 | format!("{:x}", htlc_descriptor.script_pubkey()), 58 | "0020d853877af928a8d2a569c9c0ed14bd16f6a80ce9cccaf8a6150fd8f7f8867ae2" 59 | ); 60 | 61 | // Encode the Wsh descriptor into a Bitcoin script. 62 | assert_eq!( 63 | format!("{:x}", htlc_descriptor.inner_script()), 64 | "21022222222222222222222222222222222222222222222222222222222222222222ac6476a91451814f108670aced2d77c1805ddd6634bc9d473188ad025c11b26782012088a82011111111111111111111111111111111111111111111111111111111111111118768" 65 | ); 66 | 67 | // Get the address for this Wsh descriptor.v 68 | assert_eq!( 69 | format!( 70 | "{}", 71 | htlc_descriptor.address(None, &elements::AddressParams::ELEMENTS) 72 | ), 73 | "ert1qmpfcw7he9z5d9ftfe8qw699azmm2sr8fen903fs4plv007yx0t3qdt0h29" 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /examples/parse.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2019 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Example: Parsing a descriptor from a string. 16 | 17 | extern crate elements_miniscript as miniscript; 18 | 19 | use std::str::FromStr; 20 | 21 | use crate::miniscript::descriptor::DescriptorType; 22 | use crate::miniscript::Descriptor; 23 | 24 | fn main() { 25 | let desc = miniscript::Descriptor::::from_str( 26 | "elwsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202))", 27 | ) 28 | .unwrap(); 29 | 30 | // Check whether the descriptor is safe. This checks whether all spend paths are accessible in 31 | // the Bitcoin network. It may be possible that some of the spend paths require more than 100 32 | // elements in Wsh scripts or they contain a combination of timelock and heightlock. 33 | assert!(desc.sanity_check().is_ok()); 34 | 35 | // Compute the script pubkey. As mentioned in the documentation, script_pubkey only fails 36 | // for Tr descriptors that don't have some pre-computed data. 37 | assert_eq!( 38 | format!("{:x}", desc.script_pubkey()), 39 | "0020daef16dd7c946a3e735a6e43310cb2ce33dfd14a04f76bf8241a16654cb2f0f9" 40 | ); 41 | 42 | // As another way to compute script pubkey; we can also compute the type of the descriptor. 43 | let desc_type = desc.desc_type(); 44 | assert_eq!(desc_type, DescriptorType::Wsh); 45 | // Since we know the type of descriptor, we can get the Wsh struct from Descriptor. This allows 46 | // us to call infallible methods for getting script pubkey. 47 | if let Descriptor::Wsh(wsh) = &desc { 48 | assert_eq!( 49 | format!("{:x}", wsh.script_pubkey()), 50 | "0020daef16dd7c946a3e735a6e43310cb2ce33dfd14a04f76bf8241a16654cb2f0f9" 51 | ); 52 | } 53 | 54 | // Get the inner script inside the descriptor. 55 | assert_eq!( 56 | format!( 57 | "{:x}", 58 | desc.explicit_script() 59 | .expect("Wsh descriptors have inner scripts") 60 | ), 61 | "21020202020202020202020202020202020202020202020202020202020202020202ac" 62 | ); 63 | 64 | // In a similar fashion we can parse a wrapped segwit script. 65 | let desc = miniscript::Descriptor::::from_str( 66 | "elsh(wsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202)))", 67 | ) 68 | .unwrap(); 69 | assert!(desc.desc_type() == DescriptorType::ShWsh); 70 | } 71 | -------------------------------------------------------------------------------- /examples/psbt_sign_finalize.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use elements::bitcoin::PrivateKey; 4 | use elements::encode::{serialize, serialize_hex}; 5 | use elements::hashes::Hash; 6 | use elements::sighash::SighashCache; 7 | use elements::{confidential, AssetId, LockTime, TxOutWitness}; 8 | use miniscript::elements::pset::PartiallySignedTransaction as Psbt; 9 | use miniscript::elements::{ 10 | self, pset, secp256k1_zkp as secp256k1, Address, AddressParams, OutPoint, Script, Sequence, 11 | Transaction, TxIn, TxOut, 12 | }; 13 | use miniscript::psbt::{PsbtExt, PsbtInputExt}; 14 | use miniscript::{elementssig_to_rawsig, Descriptor}; 15 | use {actual_base64 as base64, elements_miniscript as miniscript}; 16 | 17 | const ELEMENTS_PARAMS: AddressParams = AddressParams::ELEMENTS; 18 | 19 | fn main() { 20 | let secp256k1 = secp256k1::Secp256k1::new(); 21 | 22 | let s = "elwsh(t:or_c(pk(027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de),v:thresh(1,pkh(032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813),a:pkh(03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9),a:pkh(025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28))))#tdp6ld3e"; 23 | let bridge_descriptor = Descriptor::from_str(s).unwrap(); 24 | //let bridge_descriptor = Descriptor::::from_str(&s).expect("parse descriptor string"); 25 | assert!(bridge_descriptor.sanity_check().is_ok()); 26 | println!( 27 | "Bridge pubkey script: {}", 28 | bridge_descriptor.script_pubkey() 29 | ); 30 | println!( 31 | "Bridge address: {}", 32 | bridge_descriptor.address(&ELEMENTS_PARAMS).unwrap() 33 | ); 34 | println!( 35 | "Weight for witness satisfaction cost {}", 36 | bridge_descriptor.max_weight_to_satisfy().unwrap() 37 | ); 38 | 39 | let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw"; 40 | let _master_private_key = 41 | PrivateKey::from_str(master_private_key_str).expect("Can't create private key"); 42 | println!( 43 | "Master public key: {}", 44 | _master_private_key.public_key(&secp256k1) 45 | ); 46 | 47 | let backup1_private_key_str = "cWA34TkfWyHa3d4Vb2jNQvsWJGAHdCTNH73Rht7kAz6vQJcassky"; 48 | let backup1_private = 49 | PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key"); 50 | 51 | println!( 52 | "Backup1 public key: {}", 53 | backup1_private.public_key(&secp256k1) 54 | ); 55 | 56 | let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh"; 57 | let backup2_private = 58 | PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key"); 59 | 60 | println!( 61 | "Backup2 public key: {}", 62 | backup2_private.public_key(&secp256k1) 63 | ); 64 | 65 | let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6"; 66 | let _backup3_private = 67 | PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key"); 68 | 69 | println!( 70 | "Backup3 public key: {}", 71 | _backup3_private.public_key(&secp256k1) 72 | ); 73 | 74 | let spend_tx = Transaction { 75 | version: 2, 76 | lock_time: LockTime::from_height(5000).unwrap(), 77 | input: vec![], 78 | output: vec![], 79 | }; 80 | 81 | // Spend one input and spend one output for simplicity. 82 | let mut psbt = Psbt::from_tx(spend_tx); 83 | 84 | let receiver = 85 | Address::from_str("ert1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcs2yqnuv") 86 | .unwrap(); 87 | 88 | let amount = 100000000; 89 | 90 | let outpoint = elements::OutPoint { 91 | txid: elements::Txid::from_str( 92 | "7a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de", 93 | ) 94 | .unwrap(), 95 | vout: 0, 96 | }; 97 | 98 | let witness_utxo = bitcoin_asset_txout(bridge_descriptor.script_pubkey(), amount); 99 | 100 | // In practice, you would have to get the outpoint and witness utxo from the blockchain. 101 | // something like this: 102 | // let depo_tx = elements::Transction::from_str("...").unwrap(); 103 | // let (outpoint, witness_utxo) = get_vout(&depo_tx, bridge_descriptor.script_pubkey()); 104 | 105 | let txin = TxIn { 106 | previous_output: outpoint, 107 | sequence: Sequence::from_height(26), //Sequence::MAX; // 108 | ..TxIn::default() 109 | }; 110 | 111 | psbt.add_input(pset::Input::from_txin(txin)); 112 | 113 | psbt.add_output(pset::Output::from_txout(bitcoin_asset_txout( 114 | receiver.script_pubkey(), 115 | amount / 5 - 500, 116 | ))); 117 | 118 | psbt.add_output(pset::Output::from_txout(bitcoin_asset_txout( 119 | bridge_descriptor.script_pubkey(), 120 | amount * 4 / 5, 121 | ))); 122 | 123 | // Elements: Add output for fee 124 | psbt.add_output(pset::Output::from_txout(bitcoin_asset_txout( 125 | Script::new(), 126 | 500, 127 | ))); 128 | 129 | // Generating signatures & witness data 130 | 131 | psbt.inputs_mut()[0] 132 | .update_with_descriptor_unchecked(&bridge_descriptor) 133 | .unwrap(); 134 | 135 | psbt.inputs_mut()[0].witness_utxo = Some(witness_utxo); 136 | 137 | let tx = &psbt.extract_tx().unwrap(); 138 | let mut sighash_cache = SighashCache::new(tx); 139 | 140 | // genesis hash is not used at all for sighash calculation 141 | let genesis_hash = elements::BlockHash::all_zeros(); 142 | let msg = psbt 143 | .sighash_msg(0, &mut sighash_cache, None, genesis_hash) 144 | .unwrap() 145 | .to_secp_msg(); 146 | 147 | // Fixme: Take a parameter 148 | let hash_ty = elements::EcdsaSighashType::All; 149 | 150 | let sk1 = backup1_private.inner; 151 | let sk2 = backup2_private.inner; 152 | 153 | // Finally construct the signature and add to psbt 154 | let sig1 = secp256k1.sign_ecdsa(&msg, &sk1); 155 | let pk1 = backup1_private.public_key(&secp256k1); 156 | assert!(secp256k1.verify_ecdsa(&msg, &sig1, &pk1.inner).is_ok()); 157 | 158 | // Second key just in case 159 | let sig2 = secp256k1.sign_ecdsa(&msg, &sk2); 160 | let pk2 = backup2_private.public_key(&secp256k1); 161 | assert!(secp256k1.verify_ecdsa(&msg, &sig2, &pk2.inner).is_ok()); 162 | 163 | psbt.inputs_mut()[0] 164 | .partial_sigs 165 | .insert(pk1, elementssig_to_rawsig(&(sig1, hash_ty))); 166 | 167 | println!("{:#?}", psbt); 168 | 169 | let serialized = serialize(&psbt); 170 | println!("{}", base64::encode(serialized)); 171 | 172 | psbt.finalize_mut(&secp256k1, genesis_hash).unwrap(); 173 | println!("{:#?}", psbt); 174 | 175 | let tx = psbt.extract_tx().unwrap(); 176 | println!("{}", serialize_hex(&tx)); 177 | } 178 | 179 | // Find the Outpoint by spk 180 | #[allow(dead_code)] 181 | fn get_vout(tx: &Transaction, spk: Script) -> (OutPoint, TxOut) { 182 | for (i, txout) in tx.clone().output.into_iter().enumerate() { 183 | if spk == txout.script_pubkey { 184 | return (OutPoint::new(tx.txid(), i as u32), txout); 185 | } 186 | } 187 | panic!("Only call get vout on functions which have the expected outpoint"); 188 | } 189 | 190 | // Creates a bitcoin asset txout with the given explicit amount 191 | // The bitcoin asset id is hardcoded for tests and is not the actual bitcoin asset id of elements network 192 | fn bitcoin_asset_txout(spk: Script, amt: u64) -> TxOut { 193 | TxOut { 194 | script_pubkey: spk, 195 | value: confidential::Value::Explicit(amt), 196 | asset: confidential::Asset::Explicit( 197 | AssetId::from_str("088f6b381694259fd20599e71f7eb46e392f36b43cc20d131d95c8a4b8cc1aa8") 198 | .unwrap(), 199 | ), 200 | nonce: confidential::Nonce::Null, 201 | witness: TxOutWitness::default(), 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /examples/sign_multisig.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2019 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Example: Signing a 2-of-3 multisignature 16 | 17 | extern crate elements_miniscript as miniscript; 18 | 19 | use std::collections::HashMap; 20 | use std::str::FromStr; 21 | 22 | use elements::{secp256k1_zkp, LockTime, Sequence}; 23 | 24 | fn main() { 25 | // Avoid repeatedly typing a pretty-common descriptor type 26 | type BitcoinDescriptor = miniscript::Descriptor; 27 | 28 | // Transaction which spends some output 29 | let mut tx = elements::Transaction { 30 | version: 2, 31 | lock_time: LockTime::ZERO, 32 | input: vec![elements::TxIn { 33 | previous_output: elements::OutPoint::default(), 34 | script_sig: elements::Script::new(), 35 | sequence: Sequence::MAX, 36 | is_pegin: false, 37 | asset_issuance: elements::AssetIssuance::default(), 38 | witness: elements::TxInWitness::default(), 39 | }], 40 | output: vec![elements::TxOut { 41 | script_pubkey: elements::Script::new(), 42 | value: elements::confidential::Value::Explicit(100_000_000), 43 | witness: elements::TxOutWitness::default(), 44 | asset: elements::confidential::Asset::default(), 45 | nonce: elements::confidential::Nonce::default(), 46 | }], 47 | }; 48 | 49 | #[rustfmt::skip] 50 | let public_keys = [ 51 | bitcoin::PublicKey::from_slice(&[2; 33]).expect("key 1"), 52 | bitcoin::PublicKey::from_slice(&[ 53 | 0x02, 54 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 55 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 56 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | ]).expect("key 2"), 59 | bitcoin::PublicKey::from_slice(&[ 60 | 0x03, 61 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 62 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 63 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | ]).expect("key 3"), 66 | ]; 67 | let bitcoin_sig = ( 68 | // copied at random off the blockchain; this is not actually a valid 69 | // signature for this transaction; Miniscript does not verify 70 | secp256k1_zkp::ecdsa::Signature::from_str( 71 | "3045\ 72 | 0221\ 73 | 00f7c3648c390d87578cd79c8016940aa8e3511c4104cb78daa8fb8e429375efc1\ 74 | 0220\ 75 | 531d75c136272f127a5dc14acc0722301cbddc222262934151f140da345af177", 76 | ) 77 | .unwrap(), 78 | elements::EcdsaSighashType::All, 79 | ); 80 | 81 | let descriptor_str = format!( 82 | "elwsh(multi(2,{},{},{}))", 83 | public_keys[0], public_keys[1], public_keys[2], 84 | ); 85 | 86 | // Descriptor for the output being spent 87 | let my_descriptor = 88 | BitcoinDescriptor::from_str(&descriptor_str[..]).expect("parse descriptor string"); 89 | 90 | // Check weight for witness satisfaction cost ahead of time. 91 | // 106 (serialized witnessScript) 92 | // + 73*2 (signature length + signatures + sighash bytes) + 1 (dummy byte) = 253 93 | assert_eq!(my_descriptor.max_weight_to_satisfy().unwrap(), 253); 94 | 95 | // Sometimes it is necessary to have additional information to get the bitcoin::PublicKey 96 | // from the MiniscriptKey which can supplied by `to_pk_ctx` parameter. For example, 97 | // when calculating the script pubkey of a descriptor with xpubs, the secp context and 98 | // child information maybe required. 99 | 100 | // Observe the script properties, just for fun 101 | assert_eq!( 102 | format!("{:x}", my_descriptor.script_pubkey()), 103 | "00200ed49b334a12c37f3df8a2974ad91ff95029215a2b53f78155be737907f06163" 104 | ); 105 | 106 | assert_eq!( 107 | format!( 108 | "{:x}", 109 | my_descriptor 110 | .explicit_script() 111 | .expect("wsh descriptors have unique inner script") 112 | ), 113 | "52\ 114 | 21020202020202020202020202020202020202020202020202020202020202020202\ 115 | 21020102030405060708010203040506070801020304050607080000000000000000\ 116 | 21030102030405060708010203040506070801020304050607080000000000000000\ 117 | 53ae" 118 | ); 119 | 120 | // Attempt to satisfy at age 0, height 0 121 | let original_txin = tx.input[0].clone(); 122 | 123 | let mut sigs = HashMap::::new(); 124 | 125 | // Doesn't work with no signatures 126 | assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_err()); 127 | assert_eq!(tx.input[0], original_txin); 128 | 129 | // ...or one signature... 130 | sigs.insert(public_keys[1], bitcoin_sig); 131 | assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_err()); 132 | assert_eq!(tx.input[0], original_txin); 133 | 134 | // ...but two signatures is ok 135 | sigs.insert(public_keys[2], bitcoin_sig); 136 | assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_ok()); 137 | assert_ne!(tx.input[0], original_txin); 138 | assert_eq!(tx.input[0].witness.script_witness.len(), 4); // 0, sig, sig, witness script 139 | 140 | // ...and even if we give it a third signature, only two are used 141 | sigs.insert(public_keys[0], bitcoin_sig); 142 | assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_ok()); 143 | assert_ne!(tx.input[0], original_txin); 144 | assert_eq!(tx.input[0].witness.script_witness.len(), 4); // 0, sig, sig, witness script 145 | } 146 | -------------------------------------------------------------------------------- /examples/taproot.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | 3 | use std::collections::HashMap; 4 | use std::str::FromStr; 5 | 6 | use bitcoin::WitnessVersion; 7 | use miniscript::descriptor::DescriptorType; 8 | use miniscript::descriptor::TapLeafScript; 9 | use miniscript::policy::Concrete; 10 | use miniscript::{ 11 | translate_hash_fail, Descriptor, Miniscript, NoExt, Tap, TranslatePk, Translator, 12 | }; 13 | use secp256k1::{rand, Keypair}; 14 | 15 | // Refer to https://github.com/sanket1729/adv_btc_workshop/blob/master/workshop.md#creating-a-taproot-descriptor 16 | // for a detailed explanation of the policy and it's compilation 17 | 18 | struct StrPkTranslator { 19 | pk_map: HashMap, 20 | } 21 | 22 | impl Translator for StrPkTranslator { 23 | fn pk(&mut self, pk: &String) -> Result { 24 | self.pk_map.get(pk).copied().ok_or(()) 25 | } 26 | 27 | // We don't need to implement these methods as we are not using them in the policy 28 | // Fail if we encounter any hash fragments. 29 | // See also translate_hash_clone! macro 30 | translate_hash_fail!(String, bitcoin::key::XOnlyPublicKey, ()); 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 = 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("eltr(Ca,{and_v(v:pk(In),older(9)),multi_a(2,hA,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.iter_scripts(); 70 | assert_eq!( 71 | iter.next().unwrap(), 72 | ( 73 | 1, 74 | TapLeafScript::Miniscript( 75 | &Miniscript::::from_str("and_v(vc:pk_k(In),older(9))") 76 | .unwrap() 77 | ) 78 | ) 79 | ); 80 | assert_eq!( 81 | iter.next().unwrap(), 82 | ( 83 | 1, 84 | TapLeafScript::Miniscript( 85 | &Miniscript::::from_str("multi_a(2,hA,S)").unwrap() 86 | ) 87 | ) 88 | ); 89 | assert_eq!(iter.next(), None); 90 | } 91 | 92 | let mut pk_map = HashMap::new(); 93 | 94 | // We require secp for generating a random XOnlyPublicKey 95 | let secp = secp256k1::Secp256k1::new(); 96 | let key_pair = Keypair::new(&secp, &mut rand::thread_rng()); 97 | // Random unspendable XOnlyPublicKey provided for compilation to Taproot Descriptor 98 | let (unspendable_pubkey, _parity) = bitcoin::key::XOnlyPublicKey::from_keypair(&key_pair); 99 | 100 | pk_map.insert("UNSPENDABLE_KEY".to_string(), unspendable_pubkey); 101 | let pubkeys = hardcoded_xonlypubkeys(); 102 | pk_map.insert("hA".to_string(), pubkeys[0]); 103 | pk_map.insert("S".to_string(), pubkeys[1]); 104 | pk_map.insert("Ca".to_string(), pubkeys[2]); 105 | pk_map.insert("In".to_string(), pubkeys[3]); 106 | let mut t = StrPkTranslator { pk_map }; 107 | 108 | let real_desc = desc.translate_pk(&mut t).unwrap(); 109 | 110 | // Max Satisfaction Weight for compilation, corresponding to the script-path spend 111 | // `multi_a(2,PUBKEY_1,PUBKEY_2) at taptree depth 1, having 112 | // Max Witness Size = varint(control_block_size) + control_block size + 113 | // varint(script_size) + script_size + max_satisfaction_size 114 | // = 1 + 65 + 1 + 70 + 132 = 269 115 | let max_sat_wt = real_desc.max_weight_to_satisfy().unwrap(); 116 | assert_eq!(max_sat_wt, 269); 117 | 118 | // Compute the bitcoin address and check if it matches 119 | let addr = real_desc 120 | .address(&elements::AddressParams::ELEMENTS) 121 | .unwrap(); 122 | let expected_addr = elements::Address::from_str( 123 | "ert1pxx6wkfdnnx97akwws8l8xdmx5n03qftvx2t269k4sn9adm2emz0sdnytn4", 124 | ) 125 | .unwrap(); 126 | assert_eq!(addr, expected_addr); 127 | } 128 | 129 | fn hardcoded_xonlypubkeys() -> Vec { 130 | let serialized_keys: [[u8; 32]; 4] = [ 131 | [ 132 | 22, 37, 41, 4, 57, 254, 191, 38, 14, 184, 200, 133, 111, 226, 145, 183, 245, 112, 100, 133 | 42, 69, 210, 146, 60, 179, 170, 174, 247, 231, 224, 221, 52, 134 | ], 135 | [ 136 | 194, 16, 47, 19, 231, 1, 0, 143, 203, 11, 35, 148, 101, 75, 200, 15, 14, 54, 222, 208, 137 | 31, 205, 191, 215, 80, 69, 214, 126, 10, 124, 107, 154, 138 | ], 139 | [ 140 | 202, 56, 167, 245, 51, 10, 193, 145, 213, 151, 66, 122, 208, 43, 10, 17, 17, 153, 170, 141 | 29, 89, 133, 223, 134, 220, 212, 166, 138, 2, 152, 122, 16, 142 | ], 143 | [ 144 | 50, 23, 194, 4, 213, 55, 42, 210, 67, 101, 23, 3, 195, 228, 31, 70, 127, 79, 21, 188, 145 | 168, 39, 134, 58, 19, 181, 3, 63, 235, 103, 155, 213, 146 | ], 147 | ]; 148 | let mut keys: Vec = vec![]; 149 | for key in &serialized_keys { 150 | keys.push(bitcoin::key::XOnlyPublicKey::from_slice(&key[..]).unwrap()); 151 | } 152 | keys 153 | } 154 | -------------------------------------------------------------------------------- /examples/xpub_descriptors.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2019 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Example: Parsing a xpub and getting an address. 16 | 17 | extern crate elements_miniscript as miniscript; 18 | 19 | use std::str::FromStr; 20 | 21 | use elements::Address; 22 | 23 | use crate::miniscript::elements::secp256k1_zkp::{Secp256k1, Verification}; 24 | use crate::miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey}; 25 | 26 | const XPUB_1: &str = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"; 27 | const XPUB_2: &str = "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"; 28 | 29 | fn main() { 30 | // For deriving from descriptors, we need to provide a secp context. 31 | let secp = Secp256k1::verification_only(); 32 | 33 | // P2WSH and single xpubs. 34 | let _ = p2wsh(&secp); 35 | 36 | // P2WSH-P2SH and ranged xpubs. 37 | let _ = p2sh_p2wsh(&secp); 38 | } 39 | 40 | /// Parses a P2WSH descriptor, returns the associated address. 41 | fn p2wsh(secp: &Secp256k1) -> Address { 42 | // It does not matter what order the two xpubs go in, the same address will be generated. 43 | let s = format!("elwsh(sortedmulti(1,{},{}))", XPUB_1, XPUB_2); 44 | // let s = format!("wsh(sortedmulti(1,{},{}))", XPUB_2, XPUB_1); 45 | 46 | let address = Descriptor::::from_str(&s) 47 | .unwrap() 48 | .derived_descriptor(secp) 49 | .unwrap() 50 | .address(&elements::AddressParams::ELEMENTS) 51 | .unwrap(); 52 | let expected = elements::Address::from_str( 53 | "ert1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcs2yqnuv", 54 | ) 55 | .unwrap(); 56 | assert_eq!(address, expected); 57 | address 58 | } 59 | 60 | /// Parses a P2SH-P2WSH descriptor, returns the associated address. 61 | fn p2sh_p2wsh(secp: &Secp256k1) -> Address { 62 | // It does not matter what order the two xpubs go in, the same address will be generated. 63 | let s = format!( 64 | "elsh(wsh(sortedmulti(1,{}/1/0/*,{}/0/0/*)))", 65 | XPUB_1, XPUB_2 66 | ); 67 | // let s = format!("sh(wsh(sortedmulti(1,{}/1/0/*,{}/0/0/*)))", XPUB_2, XPUB_1); 68 | 69 | let address = Descriptor::::from_str(&s) 70 | .unwrap() 71 | .derived_descriptor(secp, 5) 72 | .unwrap() 73 | .address(&elements::AddressParams::ELEMENTS) 74 | .unwrap(); 75 | let expected = elements::Address::from_str("XBkDY63XnRTz6BbwzJi3ifGhBwLTomEzkq").unwrap(); 76 | assert_eq!(address, expected); 77 | address 78 | } 79 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "descriptor-fuzz" 3 | edition = "2018" 4 | version = "0.0.1" 5 | authors = ["Generated by fuzz/generate-files.sh"] 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | honggfuzz = { version = "0.5.55", default-features = false } 13 | regex = { version = "1.4"} 14 | elements-miniscript = { path = "..", features = ["compiler"] } 15 | 16 | [[bin]] 17 | name = "roundtrip_miniscript_str" 18 | path = "fuzz_targets/roundtrip_miniscript_str.rs" 19 | 20 | [[bin]] 21 | name = "roundtrip_miniscript_script" 22 | path = "fuzz_targets/roundtrip_miniscript_script.rs" 23 | 24 | [[bin]] 25 | name = "parse_descriptor" 26 | path = "fuzz_targets/parse_descriptor.rs" 27 | 28 | [[bin]] 29 | name = "roundtrip_semantic" 30 | path = "fuzz_targets/roundtrip_semantic.rs" 31 | 32 | [[bin]] 33 | name = "parse_descriptor_secret" 34 | path = "fuzz_targets/parse_descriptor_secret.rs" 35 | 36 | [[bin]] 37 | name = "roundtrip_descriptor" 38 | path = "fuzz_targets/roundtrip_descriptor.rs" 39 | 40 | [[bin]] 41 | name = "roundtrip_concrete" 42 | path = "fuzz_targets/roundtrip_concrete.rs" 43 | 44 | [[bin]] 45 | name = "compile_descriptor" 46 | path = "fuzz_targets/compile_descriptor.rs" 47 | 48 | [[bin]] 49 | name = "roundtrip_confidential" 50 | path = "fuzz_targets/roundtrip_confidential.rs" 51 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | `bitcoin` and `bitcoin_hashes` have fuzzing harnesses setup for use with 4 | honggfuzz. 5 | 6 | To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply 7 | run 8 | 9 | ./fuzz.sh 10 | 11 | in this directory. 12 | 13 | To build honggfuzz, you must have libunwind on your system, as well as 14 | libopcodes and libbfd from binutils **2.38** on your system. The most 15 | recently-released binutils 2.39 has changed their API in a breaking way. 16 | 17 | On Nix, you can obtain these libraries by running 18 | 19 | nix-shell -p libopcodes_2_38 -p libunwind 20 | 21 | and then run fuzz.sh as above. 22 | 23 | # Fuzzing with weak cryptography 24 | 25 | You may wish to replace the hashing and signing code with broken crypto, 26 | which will be faster and enable the fuzzer to do otherwise impossible 27 | things such as forging signatures or finding preimages to hashes. 28 | 29 | Doing so may result in spurious bug reports since the broken crypto does 30 | not respect the encoding or algebraic invariants upheld by the real crypto. We 31 | would like to improve this but it's a nontrivial problem -- though not 32 | beyond the abilities of a motivated student with a few months of time. 33 | Please let us know if you are interested in taking this on! 34 | 35 | Meanwhile, to use the broken crypto, simply compile (and run the fuzzing 36 | scripts) with 37 | 38 | RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz" 39 | 40 | which will replace the hashing library with broken hashes, and the 41 | secp256k1 library with broken cryptography. 42 | 43 | Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a 44 | fuzzer can break your crypto, so can anybody. 45 | 46 | # Long-term fuzzing 47 | 48 | To see the full list of targets, the most straightforward way is to run 49 | 50 | source ./fuzz-util.sh 51 | listTargetNames 52 | 53 | To run each of them for an hour, run 54 | 55 | ./cycle.sh 56 | 57 | To run a single fuzztest indefinitely, run 58 | 59 | HFUZZ_BUILD_ARGS='--features honggfuzz_fuzz' cargo hfuzz run 60 | 61 | This script uses the `chrt` utility to try to reduce the priority of the 62 | jobs. If you would like to run for longer, the most straightforward way 63 | is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, 64 | you will need to implement a custom harness. 65 | 66 | # Adding fuzz tests 67 | 68 | All fuzz tests can be found in the `fuzz_target/` directory. Adding a new 69 | one is as simple as copying an existing one and editing the `do_test` 70 | function to do what you want. 71 | 72 | If your test clearly belongs to a specific crate, please put it in that 73 | crate's directory. Otherwise you can put it directly in `fuzz_target/`. 74 | 75 | If you need to add dependencies, edit the file `generate-files.sh` to add 76 | it to the generated `Cargo.toml`. 77 | 78 | Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by 79 | running 80 | 81 | ./generate-files.sh 82 | 83 | Then to test your fuzztest, run 84 | 85 | ./fuzz.sh 86 | 87 | If it is working, you will see a rapid stream of data for many seconds 88 | (you can hit Ctrl+C to stop it early). If not, you should quickly see 89 | an error. 90 | 91 | # Reproducing Failures 92 | 93 | If a fuzztest fails, it will exit with a summary which looks something like 94 | 95 | ``` 96 | ... 97 | fuzzTarget : hfuzz_target/x86_64-unknown-linux-gnu/release/hashes_sha256 98 | CRASH: 99 | DESCRIPTION: 100 | ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov 101 | FUZZ_FNAME: hfuzz_workspace/hashes_sha256/SIGABRT.PC.7ffff7c8abc7.STACK.18826d9b64.CODE.-6.ADDR.0.INSTR.mov____%eax,%ebp.fuzz 102 | ... 103 | ===================================================================== 104 | fff400610004 105 | ``` 106 | 107 | The final line is a hex-encoded version of the input that caused the crash. You 108 | can test this directly by editing the `duplicate_crash` test to copy/paste the 109 | hex output into the call to `Vec::::from_hex`. Then run the test with 110 | 111 | cargo test 112 | 113 | Note that if you set your `RUSTFLAGS` while fuzzing (see above) you must make 114 | sure they are set the same way when running `cargo test`. 115 | 116 | -------------------------------------------------------------------------------- /fuzz/cycle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Continuosly cycle over fuzz targets running each for 1 hour. 4 | # It uses chrt SCHED_IDLE so that other process takes priority. 5 | # 6 | # For hfuzz options see https://github.com/google/honggfuzz/blob/master/docs/USAGE.md 7 | 8 | set -e 9 | REPO_DIR=$(git rev-parse --show-toplevel) 10 | # shellcheck source=./fuzz-util.sh 11 | source "$REPO_DIR/fuzz/fuzz-util.sh" 12 | 13 | while : 14 | do 15 | for targetFile in $(listTargetFiles); do 16 | targetName=$(targetFileToName "$targetFile") 17 | echo "Fuzzing target $targetName ($targetFile)" 18 | 19 | # fuzz for one hour 20 | HFUZZ_RUN_ARGS='--run_time 3600' chrt -i 0 cargo hfuzz run "$targetName" 21 | # minimize the corpus 22 | HFUZZ_RUN_ARGS="-i hfuzz_workspace/$targetName/input/ -P -M" chrt -i 0 cargo hfuzz run "$targetName" 23 | done 24 | done 25 | 26 | -------------------------------------------------------------------------------- /fuzz/fuzz-util.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | REPO_DIR=$(git rev-parse --show-toplevel) 4 | 5 | listTargetFiles() { 6 | pushd "$REPO_DIR/fuzz" > /dev/null || exit 1 7 | find fuzz_targets/ -type f -name "*.rs" 8 | popd > /dev/null || exit 1 9 | } 10 | 11 | targetFileToName() { 12 | echo "$1" \ 13 | | sed 's/^fuzz_targets\///' \ 14 | | sed 's/\.rs$//' \ 15 | | sed 's/\//_/g' 16 | } 17 | 18 | targetFileToHFuzzInputArg() { 19 | baseName=$(basename "$1") 20 | dirName="${baseName%.*}" 21 | if [ -d "hfuzz_input/$dirName" ]; then 22 | echo "HFUZZ_INPUT_ARGS=\"-f hfuzz_input/$FILE/input\"" 23 | fi 24 | } 25 | 26 | listTargetNames() { 27 | for target in $(listTargetFiles); do 28 | targetFileToName "$target" 29 | done 30 | } 31 | 32 | # Utility function to avoid CI failures on Windows 33 | checkWindowsFiles() { 34 | incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l) 35 | if [ "$incorrectFilenames" -gt 0 ]; then 36 | echo "Bailing early because there is a Windows-incompatible filename in the tree." 37 | exit 2 38 | fi 39 | } 40 | 41 | # Checks whether a fuzz case output some report, and dumps it in hex 42 | checkReport() { 43 | reportFile="hfuzz_workspace/$1/HONGGFUZZ.REPORT.TXT" 44 | if [ -f "$reportFile" ]; then 45 | cat "$reportFile" 46 | for CASE in "hfuzz_workspace/$1/SIG"*; do 47 | xxd -p -c10000 < "$CASE" 48 | done 49 | exit 1 50 | fi 51 | } 52 | -------------------------------------------------------------------------------- /fuzz/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | REPO_DIR=$(git rev-parse --show-toplevel) 5 | 6 | # shellcheck source=./fuzz-util.sh 7 | source "$REPO_DIR/fuzz/fuzz-util.sh" 8 | 9 | # Check that input files are correct Windows file names 10 | checkWindowsFiles 11 | 12 | if [ "$1" == "" ]; then 13 | targetFiles="$(listTargetFiles)" 14 | else 15 | targetFiles=fuzz_targets/"$1".rs 16 | fi 17 | 18 | cargo --version 19 | rustc --version 20 | 21 | cargo update -p byteorder --precise 1.4.3 22 | cargo update -p cc --precise 1.0.94 23 | 24 | # Testing 25 | cargo install --force honggfuzz --no-default-features 26 | for targetFile in $targetFiles; do 27 | targetName=$(targetFileToName "$targetFile") 28 | echo "Fuzzing target $targetName ($targetFile)" 29 | if [ -d "hfuzz_input/$targetName" ]; then 30 | HFUZZ_INPUT_ARGS="-f hfuzz_input/$targetName/input\"" 31 | else 32 | HFUZZ_INPUT_ARGS="" 33 | fi 34 | HFUZZ_RUN_ARGS="--run_time 30 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run "$targetName" 35 | 36 | checkReport "$targetName" 37 | done 38 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/compile_descriptor.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | 3 | use std::str::FromStr; 4 | 5 | use miniscript::{policy, Miniscript, Segwitv0}; 6 | use policy::Liftable; 7 | 8 | type Script = Miniscript; 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 | // Compile 15 | if let Ok(desc) = pol.compile::() { 16 | // Lift 17 | assert_eq!(desc.lift().unwrap().sorted(), pol.lift().unwrap().sorted()); 18 | // Try to roundtrip the output of the compiler 19 | let output = desc.to_string(); 20 | if let Ok(desc) = Script::from_str(&output) { 21 | let rtt = desc.to_string(); 22 | assert_eq!(output.to_lowercase(), rtt.to_lowercase()); 23 | } else { 24 | panic!("compiler output something unparseable: {}", output) 25 | } 26 | } 27 | } 28 | } 29 | 30 | fn main() { 31 | loop { 32 | honggfuzz::fuzz!(|data| { 33 | do_test(data); 34 | }); 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use miniscript::elements::hex::FromHex; 41 | 42 | #[test] 43 | fn duplicate_crash() { 44 | let hex = Vec::::from_hex("00").unwrap(); 45 | super::do_test(&hex); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | 3 | use std::str::FromStr; 4 | 5 | use miniscript::DescriptorPublicKey; 6 | 7 | fn do_test(data: &[u8]) { 8 | let data_str = String::from_utf8_lossy(data); 9 | if let Ok(dpk) = DescriptorPublicKey::from_str(&data_str) { 10 | let _output = dpk.to_string(); 11 | // assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 12 | } 13 | } 14 | 15 | fn main() { 16 | loop { 17 | honggfuzz::fuzz!(|data| { 18 | do_test(data); 19 | }); 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use miniscript::elements::hex::FromHex; 26 | 27 | #[test] 28 | fn duplicate_crash() { 29 | let hex = Vec::::from_hex("00").unwrap(); 30 | super::do_test(&hex); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor_secret.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | 3 | use std::str::FromStr; 4 | 5 | use miniscript::descriptor::DescriptorSecretKey; 6 | 7 | fn do_test(data: &[u8]) { 8 | let data_str = String::from_utf8_lossy(data); 9 | if let Ok(dsk) = DescriptorSecretKey::from_str(&data_str) { 10 | let output = dsk.to_string(); 11 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 12 | } 13 | } 14 | 15 | fn main() { 16 | loop { 17 | honggfuzz::fuzz!(|data| { 18 | do_test(data); 19 | }); 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use miniscript::elements::hex::FromHex; 26 | 27 | #[test] 28 | fn duplicate_crash() { 29 | let hex = Vec::::from_hex("00").unwrap(); 30 | super::do_test(&hex); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_concrete.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | extern crate regex; 3 | use std::str::FromStr; 4 | 5 | use miniscript::policy; 6 | use regex::Regex; 7 | 8 | type Policy = policy::Concrete; 9 | 10 | fn do_test(data: &[u8]) { 11 | let data_str = String::from_utf8_lossy(data); 12 | if let Ok(pol) = Policy::from_str(&data_str) { 13 | let output = pol.to_string(); 14 | //remove all instances of 1@ 15 | let re = Regex::new("(\\D)1@").unwrap(); 16 | let output = re.replace_all(&output, "$1"); 17 | let data_str = re.replace_all(&data_str, "$1"); 18 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 19 | } 20 | } 21 | 22 | fn main() { 23 | loop { 24 | honggfuzz::fuzz!(|data| { 25 | do_test(data); 26 | }); 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use miniscript::elements::hex::FromHex; 33 | 34 | #[test] 35 | fn duplicate_crash() { 36 | let hex = Vec::::from_hex("00").unwrap(); 37 | super::do_test(&hex); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_confidential.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | extern crate regex; 3 | 4 | use std::str::FromStr; 5 | 6 | use miniscript::confidential; 7 | 8 | fn do_test(data: &[u8]) { 9 | // This is how we test in rust-miniscript. It is difficult to enforce wrapping logic in fuzzer 10 | // for alias like t: and_v(1), likely and unlikely. 11 | // Just directly check whether the inferred descriptor is the same. 12 | let s = String::from_utf8_lossy(data); 13 | if let Ok(desc) = confidential::Descriptor::::from_str(&s) { 14 | let str2 = desc.to_string(); 15 | let desc2 = confidential::Descriptor::::from_str(&str2).unwrap(); 16 | 17 | assert_eq!(desc.to_string(), desc2.to_string()); 18 | } 19 | } 20 | 21 | fn main() { 22 | loop { 23 | honggfuzz::fuzz!(|data| { 24 | do_test(data); 25 | }); 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use miniscript::elements::hex::FromHex; 32 | 33 | #[test] 34 | fn duplicate_crash() { 35 | let hex = Vec::::from_hex("00").unwrap(); 36 | super::do_test(&hex); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_descriptor.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | extern crate regex; 3 | 4 | use std::str::FromStr; 5 | 6 | use miniscript::Descriptor; 7 | 8 | fn do_test(data: &[u8]) { 9 | // This is how we test in rust-miniscript. It is difficult to enforce wrapping logic in fuzzer 10 | // for alias like t: and_v(1), likely and unlikely. 11 | // Just directly check whether the inferred descriptor is the same. 12 | let s = String::from_utf8_lossy(data); 13 | if let Ok(desc) = Descriptor::::from_str(&s) { 14 | let str2 = desc.to_string(); 15 | let desc2 = Descriptor::::from_str(&str2).unwrap(); 16 | 17 | assert_eq!(desc.to_string(), desc2.to_string()); 18 | } 19 | } 20 | 21 | fn main() { 22 | loop { 23 | honggfuzz::fuzz!(|data| { 24 | do_test(data); 25 | }); 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use miniscript::elements::hex::FromHex; 32 | 33 | #[test] 34 | fn duplicate_crash() { 35 | let hex = Vec::::from_hex("00").unwrap(); 36 | super::do_test(&hex); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_script.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | 3 | use miniscript::bitcoin::PublicKey; 4 | use miniscript::elements::script; 5 | use miniscript::{Miniscript, NoExt, Segwitv0}; 6 | 7 | fn do_test(data: &[u8]) { 8 | // Try round-tripping as a script 9 | let script = script::Script::from(data.to_owned()); 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 | honggfuzz::fuzz!(|data| { 21 | do_test(data); 22 | }); 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use miniscript::elements::hex::FromHex; 29 | 30 | #[test] 31 | fn duplicate_crash() { 32 | let hex = Vec::::from_hex("00").unwrap(); 33 | super::do_test(&hex); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_str.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | extern crate regex; 3 | 4 | use std::str::FromStr; 5 | 6 | use miniscript::{Miniscript, NoExt, Segwitv0}; 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 | assert_eq!(desc, desc2); 14 | } 15 | } 16 | 17 | fn main() { 18 | loop { 19 | honggfuzz::fuzz!(|data| { 20 | do_test(data); 21 | }); 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use miniscript::elements::hex::FromHex; 28 | 29 | #[test] 30 | fn duplicate_crash() { 31 | let hex = Vec::::from_hex("00").unwrap(); 32 | super::do_test(&hex); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_semantic.rs: -------------------------------------------------------------------------------- 1 | extern crate elements_miniscript as miniscript; 2 | 3 | use std::str::FromStr; 4 | 5 | use miniscript::policy; 6 | 7 | type Policy = policy::Semantic; 8 | 9 | fn do_test(data: &[u8]) { 10 | let data_str = String::from_utf8_lossy(data); 11 | if let Ok(pol) = Policy::from_str(&data_str) { 12 | let output = pol.to_string(); 13 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 14 | } 15 | } 16 | 17 | fn main() { 18 | loop { 19 | honggfuzz::fuzz!(|data| { 20 | do_test(data); 21 | }); 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use miniscript::elements::hex::FromHex; 28 | 29 | #[test] 30 | fn duplicate_crash() { 31 | let hex = Vec::::from_hex("00").unwrap(); 32 | super::do_test(&hex); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fuzz/generate-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | REPO_DIR=$(git rev-parse --show-toplevel) 6 | 7 | # shellcheck source=./fuzz-util.sh 8 | source "$REPO_DIR/fuzz/fuzz-util.sh" 9 | 10 | # 1. Generate fuzz/Cargo.toml 11 | cat > "$REPO_DIR/fuzz/Cargo.toml" <> "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/fuzz.yml" <executed_\${{ matrix.fuzz_target }} 85 | - uses: actions/upload-artifact@v4 86 | with: 87 | name: executed_\${{ matrix.fuzz_target }} 88 | path: executed_\${{ matrix.fuzz_target }} 89 | 90 | verify-execution: 91 | if: \${{ !github.event.act }} 92 | needs: fuzz 93 | runs-on: ubuntu-latest 94 | steps: 95 | - uses: actions/checkout@v2 96 | - uses: actions/download-artifact@v2 97 | - name: Display structure of downloaded files 98 | run: ls -R 99 | - run: find executed_* -type f -exec cat {} + | sort > executed 100 | - run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed 101 | EOF 102 | 103 | -------------------------------------------------------------------------------- /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 = 60 8 | attr_fn_like_width = 70 9 | struct_lit_width = 18 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 = false 26 | where_single_line = false 27 | imports_indent = "Block" 28 | imports_layout = "Mixed" 29 | imports_granularity = "Module" # Default "Preserve" 30 | group_imports = "StdExternalCrate" # Default "Preserve" 31 | reorder_imports = true 32 | reorder_modules = true 33 | reorder_impl_items = false 34 | type_punctuation_density = "Wide" 35 | space_before_colon = false 36 | space_after_colon = true 37 | spaces_around_ranges = false 38 | binop_separator = "Front" 39 | remove_nested_parens = true 40 | combine_control_expr = true 41 | overflow_delimited_expr = false 42 | struct_field_align_threshold = 0 43 | enum_discrim_align_threshold = 0 44 | match_arm_blocks = true 45 | match_arm_leading_pipes = "Never" 46 | force_multiline_blocks = false 47 | fn_params_layout = "Tall" 48 | brace_style = "SameLineWhere" 49 | control_brace_style = "AlwaysSameLine" 50 | trailing_semicolon = true 51 | trailing_comma = "Vertical" 52 | match_block_trailing_comma = false 53 | blank_lines_upper_bound = 1 54 | blank_lines_lower_bound = 0 55 | edition = "2018" 56 | version = "One" 57 | inline_attribute_width = 0 58 | format_generated_files = true 59 | merge_derives = true 60 | use_try_shorthand = false 61 | use_field_init_shorthand = false 62 | force_explicit_abi = true 63 | condense_wildcard_suffixes = false 64 | color = "Auto" 65 | unstable_features = false 66 | disable_all_formatting = false 67 | skip_children = false 68 | hide_parse_errors = false 69 | error_on_line_overflow = false 70 | error_on_unformatted = false 71 | ignore = [] 72 | emit_mode = "Files" 73 | make_backup = false 74 | -------------------------------------------------------------------------------- /src/confidential/bare.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2023 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! "Bare Key" Confidential Descriptors 16 | 17 | use bitcoin::hashes::{sha256t_hash_newtype, Hash}; 18 | use elements::encode::Encodable; 19 | use elements::secp256k1_zkp; 20 | 21 | use crate::ToPublicKey; 22 | 23 | sha256t_hash_newtype! { 24 | pub struct TapTweakTag = hash_str("CT-Blinding-Key/1.0"); 25 | /// Taproot-tagged hash for elements tapscript Merkle tree leafs 26 | #[hash_newtype(forward)] 27 | pub struct TapTweakHash(_); 28 | } 29 | 30 | /// Tweaks a bare key using the scriptPubKey of a descriptor 31 | pub fn tweak_key<'a, Pk, V>( 32 | secp: &secp256k1_zkp::Secp256k1, 33 | spk: &elements::Script, 34 | pk: &Pk, 35 | ) -> secp256k1_zkp::PublicKey 36 | where 37 | Pk: ToPublicKey + 'a, 38 | V: secp256k1_zkp::Verification, 39 | { 40 | let mut eng = TapTweakHash::engine(); 41 | pk.to_public_key() 42 | .write_into(&mut eng) 43 | .expect("engines don't error"); 44 | spk.consensus_encode(&mut eng).expect("engines don't error"); 45 | let hash_bytes = TapTweakHash::from_engine(eng).to_byte_array(); 46 | let hash_scalar = secp256k1_zkp::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash"); 47 | pk.to_public_key() 48 | .inner 49 | .add_exp_tweak(secp, &hash_scalar) 50 | .unwrap() 51 | } 52 | 53 | /// Tweaks a bare key using the scriptPubKey of a descriptor 54 | pub fn tweak_private_key( 55 | secp: &secp256k1_zkp::Secp256k1, 56 | spk: &elements::Script, 57 | sk: &secp256k1_zkp::SecretKey, 58 | ) -> secp256k1_zkp::SecretKey 59 | where 60 | V: secp256k1_zkp::Signing, 61 | { 62 | let mut eng = TapTweakHash::engine(); 63 | bitcoin::PublicKey::new(sk.public_key(secp)) 64 | .write_into(&mut eng) 65 | .expect("engines don't error"); 66 | spk.consensus_encode(&mut eng).expect("engines don't error"); 67 | let hash_bytes = TapTweakHash::from_engine(eng).to_byte_array(); 68 | let hash_scalar = secp256k1_zkp::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash"); 69 | sk.add_tweak(&hash_scalar).unwrap() 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use bitcoin::hashes::sha256t::Tag; 75 | use bitcoin::hashes::{sha256, HashEngine}; 76 | 77 | use super::*; 78 | 79 | const MIDSTATE_HASH_TO_PRIVATE_HASH: [u8; 32] = [ 80 | 0x2f, 0x85, 0x61, 0xec, 0x30, 0x88, 0xad, 0xa9, 0x5a, 0xe7, 0x43, 0xcd, 0x3c, 0x5f, 0x59, 81 | 0x7d, 0xc0, 0x4b, 0xd0, 0x7f, 0x06, 0x5f, 0x1c, 0x06, 0x47, 0x89, 0x36, 0x63, 0xf3, 0x92, 82 | 0x6e, 0x65, 83 | ]; 84 | 85 | #[test] 86 | fn tagged_hash() { 87 | // Check that cached midstate is computed correctly 88 | // This code taken from `tag_engine` in the rust-bitcoin tests; it is identical 89 | // to that used by the BIP-0340 hashes in Taproot 90 | let mut engine = sha256::Hash::engine(); 91 | let tag_hash = sha256::Hash::hash(b"CT-Blinding-Key/1.0"); 92 | engine.input(&tag_hash[..]); 93 | engine.input(&tag_hash[..]); 94 | assert_eq!( 95 | MIDSTATE_HASH_TO_PRIVATE_HASH, 96 | engine.midstate().to_byte_array() 97 | ); 98 | 99 | // Test empty hash 100 | assert_eq!( 101 | TapTweakHash::from_engine(TapTweakTag::engine()).to_string(), 102 | "d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9", 103 | ); 104 | assert_eq!( 105 | TapTweakHash::hash(&[]).to_string(), 106 | "d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9", 107 | ); 108 | 109 | // And hash of 100 bytes 110 | let data: Vec = (0..80).collect(); 111 | assert_eq!( 112 | TapTweakHash::hash(&data).to_string(), 113 | "e1e52419a2934d278c50e29608969d2f23c1bd1243a09bfc8026d4ed4b085e39", 114 | ); 115 | } 116 | 117 | #[test] 118 | fn tweak() { 119 | // Check that tweaking blinding keys produce consistent results 120 | let secp = secp256k1_zkp::Secp256k1::new(); 121 | let sk = secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap(); 122 | let pk = sk.public_key(&secp); 123 | let spk = elements::Script::default(); 124 | let tweaked_pk = tweak_key(&secp, &spk, &pk); 125 | let tweaked_sk = tweak_private_key(&secp, &spk, &sk); 126 | assert_eq!(tweaked_pk, tweaked_sk.public_key(&secp)); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/confidential/slip77.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2022 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! SLIP77 16 | //! 17 | //! Implementation of the SLIP77 protocol, documented at 18 | //! https://github.com/satoshilabs/slips/blob/master/slip-0077.md 19 | //! 20 | 21 | use std::{borrow, fmt}; 22 | 23 | use elements::hashes::{hex, sha256, sha512, Hash, HashEngine, Hmac, HmacEngine}; 24 | use elements::secp256k1_zkp; 25 | 26 | /// A master blinding key, used for SLIP77-derived confidential addresses 27 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 | pub struct MasterBlindingKey([u8; 32]); 29 | 30 | impl fmt::Display for MasterBlindingKey { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | elements::hex::format_hex(&self.0, f) 33 | } 34 | } 35 | 36 | impl From<[u8; 32]> for MasterBlindingKey { 37 | fn from(x: [u8; 32]) -> Self { 38 | MasterBlindingKey(x) 39 | } 40 | } 41 | 42 | impl borrow::Borrow<[u8]> for MasterBlindingKey { 43 | fn borrow(&self) -> &[u8] { 44 | &self.0 45 | } 46 | } 47 | 48 | impl MasterBlindingKey { 49 | /// Compute a master blinding key from a seed 50 | /// 51 | /// The recommended in (SLIP-39) source of this seed is to obtain the 52 | /// 64-byte seed from a BIP39 derivation. 53 | pub fn from_seed(seed: &[u8]) -> Self { 54 | const DOMAIN: &[u8] = b"Symmetric key seed"; 55 | let mut root_eng = HmacEngine::::new(DOMAIN); 56 | root_eng.input(seed); 57 | let root = Hmac::from_engine(root_eng); 58 | 59 | const LABEL: &[u8] = b"SLIP-0077"; 60 | let mut node_eng = HmacEngine::::new(&root[0..32]); 61 | node_eng.input(&[0]); 62 | node_eng.input(LABEL); 63 | let node = Hmac::from_engine(node_eng); 64 | 65 | let mut ret = [0; 32]; 66 | ret.copy_from_slice(&node[32..64]); 67 | MasterBlindingKey(ret) 68 | } 69 | 70 | /// Accessor for the underlying bytes 71 | pub fn as_bytes(&self) -> &[u8] { 72 | &self.0 73 | } 74 | 75 | /// Derives a blinding private key from a given script pubkey 76 | pub fn blinding_private_key(&self, spk: &elements::Script) -> secp256k1_zkp::SecretKey { 77 | let mut eng = HmacEngine::::new(&self.0); 78 | eng.input(spk.as_bytes()); 79 | // lol why is this conversion so hard 80 | secp256k1_zkp::SecretKey::from_slice(&Hmac::from_engine(eng).to_byte_array()).unwrap() 81 | } 82 | 83 | /// Derives a public private key from a given script pubkey 84 | pub fn blinding_key( 85 | &self, 86 | secp: &secp256k1_zkp::Secp256k1, 87 | spk: &elements::Script, 88 | ) -> secp256k1_zkp::PublicKey { 89 | let sk = self.blinding_private_key(spk); 90 | secp256k1_zkp::PublicKey::from_secret_key(secp, &sk) 91 | } 92 | } 93 | 94 | impl hex::FromHex for MasterBlindingKey { 95 | type Error = hex::HexToArrayError; 96 | 97 | fn from_hex(s: &str) -> Result { 98 | Ok(MasterBlindingKey(<[u8; 32]>::from_hex(s)?)) 99 | } 100 | } 101 | 102 | impl std::str::FromStr for MasterBlindingKey { 103 | type Err = hex::HexToArrayError; 104 | fn from_str(s: &str) -> Result { 105 | hex::FromHex::from_hex(s) 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use std::str::FromStr; 112 | 113 | use elements::hashes::hex::FromHex; 114 | 115 | use super::*; 116 | 117 | fn unhex(s: &str) -> Vec { 118 | elements::hex::FromHex::from_hex(s).unwrap() 119 | } 120 | 121 | #[test] 122 | fn mbk_from_seed() { 123 | // taken from libwally src/test/test_confidential_addr.py 124 | let mbk = MasterBlindingKey::from_seed(&unhex("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8")); 125 | assert_eq!( 126 | mbk.as_bytes(), 127 | &unhex("6c2de18eabeff3f7822bc724ad482bef0557f3e1c1e1c75b7a393a5ced4de616")[..] 128 | ); 129 | 130 | let secp = secp256k1_zkp::Secp256k1::new(); 131 | let spk = elements::Script::from_str("76a914a579388225827d9f2fe9014add644487808c695d88ac") 132 | .unwrap(); 133 | let mut addr = elements::Address::from_str("2dpWh6jbhAowNsQ5agtFzi7j6nKscj6UnEr").unwrap(); 134 | addr.blinding_pubkey = Some(mbk.blinding_key(&secp, &spk)); 135 | assert_eq!( 136 | addr.to_string(), 137 | "CTEkf75DFff5ReB7juTg2oehrj41aMj21kvvJaQdWsEAQohz1EDhu7Ayh6goxpz3GZRVKidTtaXaXYEJ" 138 | ); 139 | } 140 | 141 | #[test] 142 | fn slip77_from_rust_elements() { 143 | // taken from rust-elements 144 | let mbk = MasterBlindingKey::from_seed(&unhex( 145 | "731e9b42eb9774f8a6b51af35a06f6ef1cdb6cf04402163ceacf0c8bace2831a", 146 | )); 147 | assert_eq!( 148 | mbk.as_bytes(), 149 | &unhex("c2f338e32ad1a2bd9cac569e67728163bf4c326a1770ec2293ba65548a581e97")[..] 150 | ); 151 | 152 | let spk = 153 | elements::Script::from_str("a914afa92d77cd3541b443771649572db096cf49bf8c87").unwrap(); 154 | let expected = secp256k1_zkp::SecretKey::from_slice(&unhex( 155 | "02b067c374bb56c54c016fae29218c000ada60f81ef45b4aeebbeb24931bb8bc", 156 | )) 157 | .unwrap(); 158 | assert_eq!(mbk.blinding_private_key(&spk), expected); 159 | } 160 | 161 | #[test] 162 | fn local_test_elements_22_0() { 163 | // Local test on elements 22.0 164 | let mbk = MasterBlindingKey::from_hex( 165 | "64269a8de756da06ebe35d26dccb4dd46bddcf858b54eeaae315490cfe6cacc0", 166 | ) 167 | .unwrap(); 168 | 169 | let addr = elements::Address::from_str( 170 | "el1qqg2pz79c0reryhr6hzxrzueju9m2asllwydrhexs6vj854cvwlen4tryh4thsdt2a26rte3fe87rf3my9t90wt78pcqrxv733", 171 | ) 172 | .unwrap(); 173 | 174 | let derived_blinding_key = mbk.blinding_private_key(&addr.script_pubkey()); 175 | assert_eq!( 176 | derived_blinding_key, 177 | secp256k1_zkp::SecretKey::from_slice(&unhex( 178 | "791a1081ae2ad98a5ad603737c648247f19d3c26e2beb54617638172edb230e7" 179 | )) 180 | .unwrap() 181 | ); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/descriptor/checksum.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Descriptor checksum 4 | //! 5 | //! This module contains a re-implementation of the function used by Bitcoin Core to calculate the 6 | //! checksum of a descriptor 7 | 8 | use core::fmt; 9 | use core::iter::FromIterator; 10 | 11 | use bitcoin_miniscript::expression::check_valid_chars; 12 | 13 | use crate::Error; 14 | 15 | const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; 16 | const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l"; 17 | 18 | fn poly_mod(mut c: u64, val: u64) -> u64 { 19 | let c0 = c >> 35; 20 | 21 | c = ((c & 0x7ffffffff) << 5) ^ val; 22 | if c0 & 1 > 0 { 23 | c ^= 0xf5dee51989 24 | }; 25 | if c0 & 2 > 0 { 26 | c ^= 0xa9fdca3312 27 | }; 28 | if c0 & 4 > 0 { 29 | c ^= 0x1bab10e32d 30 | }; 31 | if c0 & 8 > 0 { 32 | c ^= 0x3706b1677a 33 | }; 34 | if c0 & 16 > 0 { 35 | c ^= 0x644d626ffd 36 | }; 37 | 38 | c 39 | } 40 | 41 | /// Compute the checksum of a descriptor 42 | /// Note that this function does not check if the 43 | /// descriptor string is syntactically correct or not. 44 | /// This only computes the checksum 45 | pub fn desc_checksum(desc: &str) -> Result { 46 | let mut eng = Engine::new(); 47 | eng.input(desc)?; 48 | Ok(eng.checksum()) 49 | } 50 | 51 | /// Helper function for FromStr for various 52 | /// descriptor types. Checks and verifies the checksum 53 | /// if it is present and returns the descriptor string 54 | /// without the checksum 55 | pub(crate) fn verify_checksum(s: &str) -> Result<&str, Error> { 56 | check_valid_chars(s)?; 57 | 58 | let mut parts = s.splitn(2, '#'); 59 | let desc_str = parts.next().unwrap(); 60 | if let Some(checksum_str) = parts.next() { 61 | let expected_sum = desc_checksum(desc_str)?; 62 | if checksum_str != expected_sum { 63 | return Err(Error::BadDescriptor(format!( 64 | "Invalid checksum '{}', expected '{}'", 65 | checksum_str, expected_sum 66 | ))); 67 | } 68 | } 69 | Ok(desc_str) 70 | } 71 | 72 | /// An engine to compute a checksum from a string 73 | pub struct Engine { 74 | c: u64, 75 | cls: u64, 76 | clscount: u64, 77 | } 78 | 79 | impl Default for Engine { 80 | fn default() -> Self { 81 | Self::new() 82 | } 83 | } 84 | 85 | impl Engine { 86 | /// Construct an engine with no input 87 | pub fn new() -> Self { 88 | Engine { 89 | c: 1, 90 | cls: 0, 91 | clscount: 0, 92 | } 93 | } 94 | 95 | /// Checksum some data 96 | /// 97 | /// If this function returns an error, the `Engine` will be left in an indeterminate 98 | /// state! It is safe to continue feeding it data but the result will not be meaningful. 99 | pub fn input(&mut self, s: &str) -> Result<(), Error> { 100 | for ch in s.chars() { 101 | let pos = INPUT_CHARSET.find(ch).ok_or_else(|| { 102 | Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch)) 103 | })? as u64; 104 | self.c = poly_mod(self.c, pos & 31); 105 | self.cls = self.cls * 3 + (pos >> 5); 106 | self.clscount += 1; 107 | if self.clscount == 3 { 108 | self.c = poly_mod(self.c, self.cls); 109 | self.cls = 0; 110 | self.clscount = 0; 111 | } 112 | } 113 | Ok(()) 114 | } 115 | 116 | /// Obtain the checksum of all the data thus-far fed to the engine 117 | pub fn checksum_chars(&mut self) -> [char; 8] { 118 | if self.clscount > 0 { 119 | self.c = poly_mod(self.c, self.cls); 120 | } 121 | (0..8).for_each(|_| self.c = poly_mod(self.c, 0)); 122 | self.c ^= 1; 123 | 124 | let mut chars = [0 as char; 8]; 125 | for j in 0..8 { 126 | chars[j] = CHECKSUM_CHARSET[((self.c >> (5 * (7 - j))) & 31) as usize] as char; 127 | } 128 | chars 129 | } 130 | 131 | /// Obtain the checksum of all the data thus-far fed to the engine 132 | pub fn checksum(&mut self) -> String { 133 | String::from_iter(self.checksum_chars().iter().copied()) 134 | } 135 | } 136 | 137 | /// A wrapper around a `fmt::Formatter` which provides checksumming ability 138 | pub struct Formatter<'f, 'a> { 139 | fmt: &'f mut fmt::Formatter<'a>, 140 | eng: Engine, 141 | } 142 | 143 | impl<'f, 'a> Formatter<'f, 'a> { 144 | /// Contruct a new `Formatter`, wrapping a given `fmt::Formatter` 145 | pub fn new(f: &'f mut fmt::Formatter<'a>) -> Self { 146 | Formatter { 147 | fmt: f, 148 | eng: Engine::new(), 149 | } 150 | } 151 | 152 | /// Writes the checksum into the underlying `fmt::Formatter` 153 | pub fn write_checksum(&mut self) -> fmt::Result { 154 | use fmt::Write; 155 | self.fmt.write_char('#')?; 156 | for ch in self.eng.checksum_chars().iter().copied() { 157 | self.fmt.write_char(ch)?; 158 | } 159 | Ok(()) 160 | } 161 | 162 | /// Writes the checksum into the underlying `fmt::Formatter`, unless it has "alternate" display on 163 | pub fn write_checksum_if_not_alt(&mut self) -> fmt::Result { 164 | if !self.fmt.alternate() { 165 | self.write_checksum()?; 166 | } 167 | Ok(()) 168 | } 169 | } 170 | 171 | impl<'f, 'a> fmt::Write for Formatter<'f, 'a> { 172 | fn write_str(&mut self, s: &str) -> fmt::Result { 173 | self.fmt.write_str(s)?; 174 | self.eng.input(s).map_err(|_| fmt::Error) 175 | } 176 | } 177 | 178 | #[cfg(test)] 179 | mod test { 180 | use std::str; 181 | 182 | use super::*; 183 | 184 | macro_rules! check_expected { 185 | ($desc: expr, $checksum: expr) => { 186 | assert_eq!(desc_checksum($desc).unwrap(), $checksum); 187 | }; 188 | } 189 | 190 | #[test] 191 | fn test_valid_descriptor_checksum() { 192 | check_expected!( 193 | "elwpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", 194 | "hkvr2vkj" 195 | ); 196 | check_expected!( 197 | "elpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)", 198 | "g7zpd3we" 199 | ); 200 | 201 | // https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L352-L354 202 | check_expected!( 203 | "elsh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", 204 | "9s2ngs7u" 205 | ); 206 | check_expected!( 207 | "elsh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", 208 | "uklept69" 209 | ); 210 | } 211 | 212 | #[test] 213 | fn test_desc_checksum_invalid_character() { 214 | let sparkle_heart = vec![240, 159, 146, 150]; 215 | let sparkle_heart = str::from_utf8(&sparkle_heart) 216 | .unwrap() 217 | .chars() 218 | .next() 219 | .unwrap(); 220 | let invalid_desc = format!("elwpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart); 221 | 222 | assert_eq!( 223 | desc_checksum(&invalid_desc).err().unwrap().to_string(), 224 | format!( 225 | "Invalid descriptor: Invalid character in checksum: '{}'", 226 | sparkle_heart 227 | ) 228 | ); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/descriptor/csfs_cov/error.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2021 by 3 | // Andrew Poelstra 4 | // Sanket Kanjalkar 5 | // 6 | // To the extent possible under law, the author(s) have dedicated all 7 | // copyright and related and neighboring rights to this software to 8 | // the public domain worldwide. This software is distributed without 9 | // any warranty. 10 | // 11 | // You should have received a copy of the CC0 Public Domain Dedication 12 | // along with this software. 13 | // If not, see . 14 | // 15 | //! Covenant Descriptor Errors 16 | 17 | use std::{error, fmt}; 18 | 19 | use crate::Error; 20 | /// Covenant related Errors 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub enum CovError { 23 | /// Missing script code (segwit sighash) 24 | MissingScriptCode, 25 | /// Missing value (segwit sighash) 26 | MissingValue, 27 | /// Missing a sighash Item in satisfier, 28 | MissingSighashItem(u8), 29 | /// Missing Sighash Signature 30 | /// This must be a secp signature serialized 31 | /// in DER format *with* the sighash byte 32 | MissingCovSignature, 33 | /// Bad(Malformed) Covenant Descriptor 34 | BadCovDescriptor, 35 | /// Cannot lift a Covenant Descriptor 36 | /// This is because the different components of the covenants 37 | /// might interact across branches and thus is 38 | /// not composable and could not be analyzed individually. 39 | CovenantLift, 40 | /// The Covenant Sighash type and the satisfier sighash 41 | /// type must be the same 42 | CovenantSighashTypeMismatch, 43 | } 44 | 45 | impl fmt::Display for CovError { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | match *self { 48 | CovError::MissingScriptCode => write!(f, "Missing Script code"), 49 | CovError::MissingValue => write!(f, "Missing value"), 50 | CovError::BadCovDescriptor => write!(f, "Bad or Malformed covenant descriptor"), 51 | CovError::CovenantLift => write!(f, "Cannot lift a covenant descriptor"), 52 | CovError::MissingSighashItem(i) => { 53 | write!(f, "Missing sighash item # : {} in satisfier", i) 54 | } 55 | CovError::MissingCovSignature => write!(f, "Missing signature over the covenant pk"), 56 | CovError::CovenantSighashTypeMismatch => write!( 57 | f, 58 | "The sighash type provided in the witness must the same \ 59 | as the one used in signature" 60 | ), 61 | } 62 | } 63 | } 64 | 65 | impl error::Error for CovError {} 66 | 67 | #[doc(hidden)] 68 | impl From for Error { 69 | fn from(e: CovError) -> Error { 70 | Error::CovError(e) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/descriptor/csfs_cov/satisfy.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2021 by 3 | // Andrew Poelstra 4 | // Sanket Kanjalkar 5 | // 6 | // To the extent possible under law, the author(s) have dedicated all 7 | // copyright and related and neighboring rights to this software to 8 | // the public domain worldwide. This software is distributed without 9 | // any warranty. 10 | // 11 | // You should have received a copy of the CC0 Public Domain Dedication 12 | // along with this software. 13 | // If not, see . 14 | // 15 | //! Covenant Descriptor Satisfaction 16 | 17 | use elements::encode::Encodable; 18 | use elements::hashes::{sha256d, Hash}; 19 | use elements::sighash::SighashCache; 20 | use elements::{self, confidential, EcdsaSighashType, OutPoint, Script, Sighash, Transaction}; 21 | 22 | use super::CovError; 23 | use crate::{MiniscriptKey, Satisfier, ToPublicKey}; 24 | 25 | /// A satisfier for Covenant descriptors 26 | /// that can do transaction introspection 27 | /// 'tx denotes the lifetime of the transaction 28 | /// being satisfied and 'ptx denotes the lifetime 29 | /// of the previous transaction inputs 30 | #[derive(Debug, Clone, PartialEq, Eq)] 31 | pub struct LegacyCovSatisfier<'tx, 'ptx> { 32 | // Common fields in Segwit and Taphash 33 | /// The transaction being spent 34 | tx: &'tx Transaction, 35 | /// The script code required for 36 | /// The input index being spent 37 | idx: u32, 38 | /// The sighash type 39 | hash_type: EcdsaSighashType, 40 | 41 | // Segwitv0 42 | /// The script code required for segwit sighash 43 | script_code: Option<&'ptx Script>, 44 | /// The value of the output being spent 45 | value: Option, 46 | } 47 | 48 | impl<'tx, 'ptx> LegacyCovSatisfier<'tx, 'ptx> { 49 | /// Create a new Covsatisfier for v0 spends 50 | /// Panics if idx is out of bounds 51 | pub fn new_segwitv0( 52 | tx: &'tx Transaction, 53 | idx: u32, 54 | value: confidential::Value, 55 | script_code: &'ptx Script, 56 | hash_type: EcdsaSighashType, 57 | ) -> Self { 58 | assert!((idx as usize) < tx.input.len()); 59 | Self { 60 | tx, 61 | idx, 62 | hash_type, 63 | script_code: Some(script_code), 64 | value: Some(value), 65 | } 66 | } 67 | 68 | /// Easy way to get sighash since we already have 69 | /// all the required information. 70 | /// Note that this does not do any caching, so it 71 | /// will be slightly inefficient as compared to 72 | /// using sighash 73 | pub fn segwit_sighash(&self) -> Result { 74 | let mut cache = SighashCache::new(self.tx); 75 | // TODO: error types 76 | let script_code = self.script_code.ok_or(CovError::MissingScriptCode)?; 77 | let value = self.value.ok_or(CovError::MissingValue)?; 78 | Ok(cache.segwitv0_sighash(self.idx as usize, script_code, value, self.hash_type)) 79 | } 80 | } 81 | 82 | impl<'tx, 'ptx, Pk: MiniscriptKey + ToPublicKey> Satisfier for LegacyCovSatisfier<'tx, 'ptx> { 83 | fn lookup_nversion(&self) -> Option { 84 | Some(self.tx.version) 85 | } 86 | 87 | fn lookup_hashprevouts(&self) -> Option { 88 | let mut enc = sha256d::Hash::engine(); 89 | for txin in &self.tx.input { 90 | txin.previous_output.consensus_encode(&mut enc).unwrap(); 91 | } 92 | Some(sha256d::Hash::from_engine(enc)) 93 | } 94 | 95 | fn lookup_hashsequence(&self) -> Option { 96 | let mut enc = sha256d::Hash::engine(); 97 | for txin in &self.tx.input { 98 | txin.sequence.consensus_encode(&mut enc).unwrap(); 99 | } 100 | Some(sha256d::Hash::from_engine(enc)) 101 | } 102 | 103 | fn lookup_hashissuances(&self) -> Option { 104 | let mut enc = sha256d::Hash::engine(); 105 | for txin in &self.tx.input { 106 | if txin.has_issuance() { 107 | txin.asset_issuance.consensus_encode(&mut enc).unwrap(); 108 | } else { 109 | 0u8.consensus_encode(&mut enc).unwrap(); 110 | } 111 | } 112 | Some(sha256d::Hash::from_engine(enc)) 113 | } 114 | 115 | fn lookup_outpoint(&self) -> Option { 116 | Some(self.tx.input[self.idx as usize].previous_output) 117 | } 118 | 119 | fn lookup_scriptcode(&self) -> Option<&Script> { 120 | self.script_code 121 | } 122 | 123 | fn lookup_value(&self) -> Option { 124 | self.value 125 | } 126 | 127 | fn lookup_nsequence(&self) -> Option { 128 | Some(self.tx.input[self.idx as usize].sequence.to_consensus_u32()) 129 | } 130 | 131 | fn lookup_outputs(&self) -> Option<&[elements::TxOut]> { 132 | Some(&self.tx.output) 133 | } 134 | 135 | fn lookup_nlocktime(&self) -> Option { 136 | Some(self.tx.lock_time.to_consensus_u32()) 137 | } 138 | 139 | fn lookup_sighashu32(&self) -> Option { 140 | Some(self.hash_type.as_u32()) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/descriptor/csfs_cov/script_internals.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2021 by 3 | // Andrew Poelstra 4 | // Sanket Kanjalkar 5 | // 6 | // To the extent possible under law, the author(s) have dedicated all 7 | // copyright and related and neighboring rights to this software to 8 | // the public domain worldwide. This software is distributed without 9 | // any warranty. 10 | // 11 | // You should have received a copy of the CC0 Public Domain Dedication 12 | // along with this software. 13 | // If not, see . 14 | // 15 | //! Script Internals for Covenant Descriptor Support 16 | 17 | use bitcoin; 18 | use elements::opcodes::all; 19 | use elements::script; 20 | /// Additional operations required on script builder 21 | /// for Covenant operations support 22 | pub trait CovOperations: Sized { 23 | /// Assert that the size of top stack elem is `len` 24 | fn chk_size(self, len: usize) -> Self; 25 | /// Assert that the top item is a valid confidential Amount 26 | /// If it starts with 1, the len must be 9, otherwise the 27 | /// len must be 33 28 | fn chk_amt(self) -> Self; 29 | /// Assuming the 10 sighash components + 1 sig on the top of 30 | /// stack for segwit sighash as created by init_stack 31 | /// CAT all of them and check sig from stack 32 | fn verify_cov(self, key: &bitcoin::PublicKey) -> Self; 33 | 34 | /// Get the script code for the covenant script 35 | /// assuming the above construction of covenants 36 | /// which uses OP_CODESEP 37 | fn post_codesep_script(self) -> Self; 38 | } 39 | 40 | impl CovOperations for script::Builder { 41 | fn chk_size(self, len: usize) -> Self { 42 | self.push_opcode(all::OP_SIZE) 43 | .push_int(len as i64) 44 | .push_opcode(all::OP_EQUALVERIFY) 45 | } 46 | 47 | fn chk_amt(self) -> Self { 48 | let mut builder = self; 49 | 50 | // Copy the first byte onto the stack 51 | builder = builder.push_opcode(all::OP_DUP); 52 | builder = builder.push_int(1).push_opcode(all::OP_LEFT); 53 | // Check if first byte is equal to 1 54 | builder = builder.push_int(1).push_opcode(all::OP_EQUAL); 55 | // Assert that explicit size is 9 56 | builder = builder 57 | .push_opcode(all::OP_IF) 58 | .push_opcode(all::OP_SIZE) 59 | .push_int(9) 60 | .push_opcode(all::OP_EQUALVERIFY); 61 | // Else assert that commitment size is 33 62 | builder 63 | .push_opcode(all::OP_ELSE) 64 | .push_opcode(all::OP_SIZE) 65 | .push_int(33) 66 | .push_opcode(all::OP_EQUALVERIFY) 67 | .push_opcode(all::OP_ENDIF) 68 | } 69 | 70 | #[rustfmt::skip] 71 | fn verify_cov(self, key: &bitcoin::PublicKey) -> Self { 72 | use elements::opcodes::all::{OP_CAT, OP_SWAP}; 73 | 74 | let mut builder = self; 75 | // The miniscript is of type B, which should have pushed 1 76 | // onto the stack if it satisfied correctly.(which it should) 77 | // because this is a top level check 78 | // Initially the stack contains the [ec_sig..sighash_items] 79 | // where sighash_items are items from segwit bip143 sighash for 80 | // elements arranged sequentially such that item 1 is at top, 81 | // item 10 is the last. The top of stack is miniscript execution result 82 | // denoted by B type 83 | // stk = [ecsig i10 i9 i8 i7 i6 i5 i4 i3b i3 i2 i1 B] 84 | // alt_stk = [] 85 | builder = builder.push_verify(); 86 | // stk = [ecsig i10 i9 i8 i7 i6 i5 i4 i3b i3 i2 i1] 87 | // alt_stk = [] 88 | // pick signature. stk_size = 12 89 | // Why can we pick have a fixed pick of 11? 90 | // The covenant check enforces that the the next 12 elements 91 | // of the stack must be elements from the sighash. 92 | // We don't additionally need to check the depth because 93 | // cleanstack is a consensus rule in segwit. 94 | // Copy the ec_sig to the stack top 95 | builder = builder.push_int(11).push_opcode(all::OP_PICK); 96 | // convert sighash type into 1 byte(It is 4 byte in sighash calculation) 97 | // Since we copied the ecsig onto stack top, this will now be at pos 11 98 | builder = builder.push_int(11).push_opcode(all::OP_PICK); 99 | builder = builder.push_int(1).push_opcode(all::OP_LEFT); 100 | // create a bitcoinsig = [ecsig || sighashtype]cat the sig and hashtype 101 | builder = builder.push_opcode(all::OP_CAT); 102 | // Push the bitcoinsig to alt stack 103 | builder = builder.push_opcode(all::OP_TOALTSTACK); 104 | // alt_stk = [bitcoinsig] 105 | // stk = [ecsig i10 i9 i8 i7 i6 i5 i4 i3b i3 i2 i1] 106 | 107 | // Do the size checks on all respective items in sighash calculation 108 | builder = builder.chk_size(4).push_opcode(OP_SWAP); // item 1: ver 109 | builder = builder.chk_size(32).push_opcode(OP_CAT).push_opcode(OP_SWAP);//item 2: hashprevouts 110 | builder = builder.chk_size(32).push_opcode(OP_CAT).push_opcode(OP_SWAP);//item 3: hashsequence 111 | builder = builder.chk_size(32).push_opcode(OP_CAT).push_opcode(OP_SWAP);//item 3b: hashissuances 112 | builder = builder.chk_size(36).push_opcode(OP_CAT).push_opcode(OP_SWAP);//item 4: outpoint 113 | // Item 5: Script code is of constant size because we only consider everything after 114 | // codeseparator. This will be replaced with a push slice in a later commit 115 | builder = builder.chk_size(3).push_opcode(OP_CAT).push_opcode(OP_SWAP); //item 5: script code 116 | builder = builder.chk_amt().push_opcode(OP_CAT).push_opcode(OP_SWAP); //item 6: check confAmt 117 | builder = builder.chk_size(4).push_opcode(OP_CAT).push_opcode(OP_SWAP); //item 7: sequence 118 | builder = builder.chk_size(32).push_opcode(OP_CAT).push_opcode(OP_SWAP);//item 8: hashoutputs 119 | builder = builder.chk_size(4).push_opcode(OP_CAT).push_opcode(OP_SWAP); //item 9: nlocktime 120 | builder = builder.chk_size(4).push_opcode(OP_CAT); //item 10: sighash type 121 | 122 | // Now sighash is on the top of the stack 123 | // alt_stk = [bitcoinsig] 124 | // stk = [ecsig (i1||i2||i3||i3b||i4||i5||i6||i7||i8||i9||i10)] 125 | // Note that item order is reversed 126 | // || denotes concat operation 127 | builder = builder.push_opcode(all::OP_SHA256); 128 | builder = builder.push_key(key).push_opcode(all::OP_DUP); 129 | builder = builder 130 | .push_opcode(all::OP_FROMALTSTACK) 131 | .push_opcode(all::OP_SWAP); 132 | // stk = [ecsig sha2_msg pk btcsig pk] 133 | // alt_stk = [] 134 | 135 | // Code separator. Everything before this(and including this codesep) 136 | // won't be used in script code calculation 137 | builder = builder.push_opcode(all::OP_CODESEPARATOR); 138 | builder.post_codesep_script() 139 | } 140 | 141 | /// The second parameter decides whether the script code should 142 | /// a hashlock verifying the entire script 143 | fn post_codesep_script(self) -> Self { 144 | let builder = self; 145 | // Now sighash is on the top of the stack 146 | // stk = [ecsig sha2_msg pk btcsig pk] 147 | builder 148 | .push_opcode(all::OP_CHECKSIGVERIFY) 149 | .push_opcode(all::OP_CHECKSIGFROMSTACK) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/descriptor/pegin/mod.rs: -------------------------------------------------------------------------------- 1 | // Miniscript 2 | // Written in 2018 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Pegin Descriptor Support 16 | //! 17 | //! Traits and implementations for Pegin descriptors 18 | //! Note that this is a bitcoin descriptor and thus cannot be 19 | //! added to elements Descriptor. Upstream rust-miniscript does 20 | //! has a Descriptor enum which should ideally have it, but 21 | //! bitcoin descriptors cannot depend on elements descriptors 22 | //! Thus, as a simple solution we implement these as a separate 23 | //! struct with it's own API. 24 | 25 | pub mod dynafed_pegin; 26 | pub mod legacy_pegin; 27 | pub use self::dynafed_pegin::Pegin; 28 | pub use self::legacy_pegin::{LegacyPegin, LegacyPeginKey}; 29 | -------------------------------------------------------------------------------- /src/expression.rs: -------------------------------------------------------------------------------- 1 | // Written in 2019 by Andrew Poelstra 2 | // SPDX-License-Identifier: CC0-1.0 3 | 4 | //! # Function-like Expression Language 5 | //! 6 | 7 | use std::fmt; 8 | use std::str::FromStr; 9 | 10 | use bitcoin_miniscript::expression::check_valid_chars; 11 | 12 | use crate::{errstr, Error, MAX_RECURSION_DEPTH}; 13 | 14 | #[derive(Debug, Clone)] 15 | /// A token of the form `x(...)` or `x` 16 | pub struct Tree<'a> { 17 | /// The name `x` 18 | pub name: &'a str, 19 | /// The comma-separated contents of the `(...)`, if any 20 | pub args: Vec>, 21 | } 22 | // or_b(pk(A),pk(B)) 23 | // 24 | // A = musig(musig(B,C),D,E) 25 | // or_b() 26 | // pk(A), pk(B) 27 | 28 | /// A trait for extracting a structure from a Tree representation in token form 29 | pub trait FromTree: Sized { 30 | /// Extract a structure from Tree representation 31 | fn from_tree(top: &Tree<'_>) -> Result; 32 | } 33 | 34 | impl<'a> fmt::Display for Tree<'a> { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | write!(f, "({}", self.name)?; 37 | for arg in &self.args { 38 | write!(f, ",{}", arg)?; 39 | } 40 | write!(f, ")") 41 | } 42 | } 43 | 44 | enum Found { 45 | Nothing, 46 | LBracket(usize), // Either a left ( or { 47 | Comma(usize), 48 | RBracket(usize), // Either a right ) or } 49 | } 50 | 51 | fn next_expr(sl: &str, delim: char) -> Found { 52 | // Decide whether we are parsing a key or not. 53 | // When parsing a key ignore all the '(' and ')'. 54 | // We keep count of lparan whenever we are inside a key context 55 | // We exit the context whenever we find the corresponding ')' 56 | // in which we entered the context. This allows to special case 57 | // parse the '(' ')' inside key expressions.(key or musig(keys)). 58 | let mut key_ctx = false; 59 | let mut key_lparan_count = 0; 60 | let mut found = Found::Nothing; 61 | if delim == '(' { 62 | for (n, ch) in sl.char_indices() { 63 | match ch { 64 | '(' => { 65 | // already inside a key context 66 | if key_ctx { 67 | key_lparan_count += 1; 68 | } else if &sl[..n] == "musig" { 69 | key_lparan_count = 1; 70 | key_ctx = true; 71 | } else { 72 | found = Found::LBracket(n); 73 | break; 74 | } 75 | } 76 | ',' => { 77 | if !key_ctx { 78 | found = Found::Comma(n); 79 | break; 80 | } 81 | } 82 | ')' => { 83 | if key_ctx { 84 | key_lparan_count -= 1; 85 | if key_lparan_count == 0 { 86 | key_ctx = false; 87 | } 88 | } else { 89 | found = Found::RBracket(n); 90 | break; 91 | } 92 | } 93 | _ => {} 94 | } 95 | } 96 | } else if delim == '{' { 97 | let mut new_count = 0; 98 | for (n, ch) in sl.char_indices() { 99 | match ch { 100 | '{' => { 101 | found = Found::LBracket(n); 102 | break; 103 | } 104 | '(' => { 105 | new_count += 1; 106 | } 107 | ',' => { 108 | if new_count == 0 { 109 | found = Found::Comma(n); 110 | break; 111 | } 112 | } 113 | ')' => { 114 | new_count -= 1; 115 | } 116 | '}' => { 117 | found = Found::RBracket(n); 118 | break; 119 | } 120 | _ => {} 121 | } 122 | } 123 | } else { 124 | unreachable!("{}", "Internal: delimiters in parsing must be '(' or '{'"); 125 | } 126 | found 127 | } 128 | 129 | // Get the corresponding delim 130 | fn closing_delim(delim: char) -> char { 131 | match delim { 132 | '(' => ')', 133 | '{' => '}', 134 | _ => unreachable!("Unknown delimiter"), 135 | } 136 | } 137 | 138 | impl<'a> Tree<'a> { 139 | /// Parse an expression with round brackets 140 | pub fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> { 141 | // Parsing TapTree or just miniscript 142 | Self::from_slice_delim(sl, 0u32, '(') 143 | } 144 | 145 | pub(crate) fn from_slice_delim( 146 | mut sl: &'a str, 147 | depth: u32, 148 | delim: char, 149 | ) -> Result<(Tree<'a>, &'a str), Error> { 150 | if depth >= MAX_RECURSION_DEPTH { 151 | return Err(Error::MaxRecursiveDepthExceeded); 152 | } 153 | 154 | match next_expr(sl, delim) { 155 | // String-ending terminal 156 | Found::Nothing => Ok(( 157 | Tree { 158 | name: sl, 159 | args: vec![], 160 | }, 161 | "", 162 | )), 163 | // Terminal 164 | Found::Comma(n) | Found::RBracket(n) => Ok(( 165 | Tree { 166 | name: &sl[..n], 167 | args: vec![], 168 | }, 169 | &sl[n..], 170 | )), 171 | // Function call 172 | Found::LBracket(n) => { 173 | let mut ret = Tree { 174 | name: &sl[..n], 175 | args: vec![], 176 | }; 177 | 178 | sl = &sl[n + 1..]; 179 | loop { 180 | let (arg, new_sl) = Tree::from_slice_delim(sl, depth + 1, delim)?; 181 | ret.args.push(arg); 182 | 183 | if new_sl.is_empty() { 184 | return Err(Error::ExpectedChar(closing_delim(delim))); 185 | } 186 | 187 | sl = &new_sl[1..]; 188 | match new_sl.as_bytes()[0] { 189 | b',' => {} 190 | last_byte => { 191 | if last_byte == closing_delim(delim) as u8 { 192 | break; 193 | } else { 194 | return Err(Error::ExpectedChar(closing_delim(delim))); 195 | } 196 | } 197 | } 198 | } 199 | Ok((ret, sl)) 200 | } 201 | } 202 | } 203 | 204 | /// Parses a tree from a string 205 | #[allow(clippy::should_implement_trait)] // seems to be a false positive 206 | pub fn from_str(s: &'a str) -> Result, Error> { 207 | check_valid_chars(s)?; 208 | 209 | let (top, rem) = Tree::from_slice(s)?; 210 | if rem.is_empty() { 211 | Ok(top) 212 | } else { 213 | Err(errstr(rem)) 214 | } 215 | } 216 | } 217 | 218 | /// Parse a string as a u32, for timelocks or thresholds 219 | pub fn parse_num(s: &str) -> Result { 220 | if s.len() > 1 { 221 | let ch = s.chars().next().unwrap(); 222 | let ch = if ch == '-' { 223 | s.chars().nth(1).ok_or(Error::Unexpected( 224 | "Negative number must follow dash sign".to_string(), 225 | ))? 226 | } else { 227 | ch 228 | }; 229 | if !('1'..='9').contains(&ch) { 230 | return Err(Error::Unexpected( 231 | "Number must start with a digit 1-9".to_string(), 232 | )); 233 | } 234 | } 235 | T::from_str(s).map_err(|_| errstr(s)) 236 | } 237 | 238 | /// Attempts to parse a terminal expression 239 | pub fn terminal(term: &Tree<'_>, convert: F) -> Result 240 | where 241 | F: FnOnce(&str) -> Result, 242 | Err: ToString, 243 | { 244 | if term.args.is_empty() { 245 | convert(term.name).map_err(|e| Error::Unexpected(e.to_string())) 246 | } else { 247 | Err(errstr(term.name)) 248 | } 249 | } 250 | 251 | /// Attempts to parse an expression with exactly one child 252 | pub fn unary(term: &Tree<'_>, convert: F) -> Result 253 | where 254 | L: FromTree, 255 | F: FnOnce(L) -> T, 256 | { 257 | if term.args.len() == 1 { 258 | let left = FromTree::from_tree(&term.args[0])?; 259 | Ok(convert(left)) 260 | } else { 261 | Err(errstr(term.name)) 262 | } 263 | } 264 | 265 | /// Attempts to parse an expression with exactly two children 266 | pub fn binary(term: &Tree<'_>, convert: F) -> Result 267 | where 268 | L: FromTree, 269 | R: FromTree, 270 | F: FnOnce(L, R) -> T, 271 | { 272 | if term.args.len() == 2 { 273 | let left = FromTree::from_tree(&term.args[0])?; 274 | let right = FromTree::from_tree(&term.args[1])?; 275 | Ok(convert(left, right)) 276 | } else { 277 | Err(errstr(term.name)) 278 | } 279 | } 280 | 281 | #[cfg(test)] 282 | mod tests { 283 | 284 | use super::parse_num; 285 | 286 | #[test] 287 | fn test_parse_num() { 288 | assert!(parse_num::("0").is_ok()); 289 | assert!(parse_num::("00").is_err()); 290 | assert!(parse_num::("0000").is_err()); 291 | assert!(parse_num::("06").is_err()); 292 | assert!(parse_num::("+6").is_err()); 293 | assert!(parse_num::("-6").is_err()); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/extensions/index_ops.rs: -------------------------------------------------------------------------------- 1 | //! Miniscript Index expressions: 2 | //! Note that these fragment is only supported for Tapscript context 3 | //! Refer to the spec for additional details. 4 | use std::fmt; 5 | 6 | use elements::opcodes::{self}; 7 | use elements::script; 8 | 9 | use super::{EvalError, TxEnv}; 10 | use crate::expression::{FromTree, Tree}; 11 | use crate::miniscript::lex::Token as Tk; 12 | use crate::{expression, script_num_size, Error}; 13 | 14 | /// Enum representing operations with input/output indexes. 15 | /// Pushes a single CScriptNum on stack top. This is used to represent the index of the input or output. 16 | #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)] 17 | pub enum IdxExpr { 18 | /* leaf fragments/terminals */ 19 | /// A constant 20 | /// `` as CScriptNum 21 | Const(usize), 22 | /// Current Input index 23 | CurrIdx, 24 | /// Add two IdxExpr 25 | /// `[X] [Y] ADD` 26 | Add(Box, Box), 27 | /// Subtract two IdxExpr 28 | /// `[X] [Y] SUB` 29 | Sub(Box, Box), 30 | /// Multiply two IdxExpr 31 | /// `[X] SCIPTNUMTOLE64 [Y] SCIPTNUMTOLE64 MUL64 <1> EQUALVERIFY LE64TOSCIPTNUM` 32 | Mul(Box, Box), 33 | /// Divide two IdxExpr (integer division) 34 | /// `[X] SCIPTNUMTOLE64 [Y] SCIPTNUMTOLE64 DIV64 <1> EQUALVERIFY NIP LE64TOSCIPTNUM` 35 | Div(Box, Box), 36 | } 37 | 38 | impl IdxExpr { 39 | /// Returns the script size of this [`IdxExpr`]. 40 | pub fn script_size(&self) -> usize { 41 | match self { 42 | IdxExpr::Const(i) => script_num_size(*i), 43 | IdxExpr::CurrIdx => 1, 44 | IdxExpr::Add(x, y) => x.script_size() + y.script_size() + 1, 45 | IdxExpr::Sub(x, y) => x.script_size() + y.script_size() + 1, 46 | IdxExpr::Mul(x, y) => x.script_size() + y.script_size() + 6, 47 | IdxExpr::Div(x, y) => x.script_size() + y.script_size() + 7, 48 | } 49 | } 50 | } 51 | 52 | impl fmt::Display for IdxExpr { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | match self { 55 | IdxExpr::Const(i) => write!(f, "{}", i), 56 | IdxExpr::CurrIdx => write!(f, "curr_idx"), 57 | IdxExpr::Add(x, y) => write!(f, "idx_add({},{})", x, y), 58 | IdxExpr::Sub(x, y) => write!(f, "idx_sub({},{})", x, y), 59 | IdxExpr::Mul(x, y) => write!(f, "idx_mul({},{})", x, y), 60 | IdxExpr::Div(x, y) => write!(f, "idx_div({},{})", x, y), 61 | } 62 | } 63 | } 64 | 65 | impl fmt::Debug for IdxExpr { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | match self { 68 | IdxExpr::Const(i) => write!(f, "{:?}", i), 69 | IdxExpr::CurrIdx => write!(f, "curr_idx"), 70 | IdxExpr::Add(x, y) => write!(f, "idx_add({:?},{:?})", x, y), 71 | IdxExpr::Sub(x, y) => write!(f, "idx_sub({:?},{:?})", x, y), 72 | IdxExpr::Mul(x, y) => write!(f, "idx_mul({:?},{:?})", x, y), 73 | IdxExpr::Div(x, y) => write!(f, "idx_div({:?},{:?})", x, y), 74 | } 75 | } 76 | } 77 | 78 | impl FromTree for IdxExpr { 79 | fn from_tree(top: &Tree<'_>) -> Result { 80 | match (top.name, top.args.len()) { 81 | ("curr_idx", 0) => Ok(IdxExpr::CurrIdx), 82 | ("idx_add", 2) => Ok(IdxExpr::Add( 83 | Box::new(Self::from_tree(&top.args[0])?), 84 | Box::new(Self::from_tree(&top.args[1])?), 85 | )), 86 | ("idx_sub", 2) => Ok(IdxExpr::Sub( 87 | Box::new(Self::from_tree(&top.args[0])?), 88 | Box::new(Self::from_tree(&top.args[1])?), 89 | )), 90 | ("idx_mul", 2) => Ok(IdxExpr::Mul( 91 | Box::new(Self::from_tree(&top.args[0])?), 92 | Box::new(Self::from_tree(&top.args[1])?), 93 | )), 94 | ("idx_div", 2) => Ok(IdxExpr::Div( 95 | Box::new(Self::from_tree(&top.args[0])?), 96 | Box::new(Self::from_tree(&top.args[1])?), 97 | )), 98 | (_num, 0) => { 99 | expression::terminal(top, expression::parse_num::).map(IdxExpr::Const) 100 | } 101 | _ => Err(Error::Unexpected(format!("Unexpected token: {:?}", top))), 102 | } 103 | } 104 | } 105 | 106 | impl IdxExpr { 107 | /// Push this script to builder 108 | /// Panics when trying to push a Null asset. This never occur in honest use-cases 109 | /// as there is no such thing as Null asset 110 | pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder { 111 | use opcodes::all::*; 112 | match self { 113 | IdxExpr::Const(i) => builder.push_int(*i as i64), 114 | IdxExpr::CurrIdx => builder.push_opcode(OP_PUSHCURRENTINPUTINDEX), 115 | IdxExpr::Add(x, y) => { 116 | let builder = x.push_to_builder(builder); 117 | let builder = y.push_to_builder(builder); 118 | builder.push_opcode(OP_ADD) 119 | } 120 | IdxExpr::Sub(x, y) => { 121 | let builder = x.push_to_builder(builder); 122 | let builder = y.push_to_builder(builder); 123 | builder.push_opcode(OP_SUB) 124 | } 125 | IdxExpr::Mul(x, y) => { 126 | let builder = x.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64); 127 | let builder = y.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64); 128 | builder 129 | .push_opcode(OP_MUL64) 130 | .push_int(1) 131 | .push_opcode(OP_EQUALVERIFY) 132 | .push_opcode(OP_LE64TOSCRIPTNUM) 133 | } 134 | IdxExpr::Div(x, y) => { 135 | let builder = x.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64); 136 | let builder = y.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64); 137 | builder 138 | .push_opcode(OP_DIV64) 139 | .push_int(1) 140 | .push_opcode(OP_EQUALVERIFY) 141 | .push_opcode(OP_NIP) 142 | .push_opcode(OP_LE64TOSCRIPTNUM) 143 | } 144 | } 145 | } 146 | 147 | /// Evaluate this expression 148 | pub fn eval(&self, env: &TxEnv) -> Result { 149 | match self { 150 | IdxExpr::Const(i) => Ok(*i), 151 | IdxExpr::CurrIdx => Ok(env.idx), 152 | IdxExpr::Add(x, y) => Ok(x.eval(env)? + y.eval(env)?), 153 | IdxExpr::Sub(x, y) => Ok(x.eval(env)? - y.eval(env)?), 154 | IdxExpr::Mul(x, y) => Ok(x.eval(env)? * y.eval(env)?), 155 | IdxExpr::Div(x, y) => Ok(x.eval(env)? / y.eval(env)?), 156 | } 157 | } 158 | 159 | /// Returns (self, start_pos) parsed reversed form tokens starting with index end_pos 160 | /// Expression is parsed from tokens `[start:end_pos]` 161 | #[rustfmt::skip] 162 | pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> { 163 | let tks = tokens; 164 | let e = end_pos; // short abbreviations for succinct readable code 165 | if let Some(&[Tk::Num(i)]) = tks.get(e.checked_sub(1)?..e) { 166 | Some((IdxExpr::Const(i as usize), e - 1)) 167 | } else if let Some(&[Tk::CurrInp]) = tks.get(e.checked_sub(1)?..e) { 168 | Some((IdxExpr::CurrIdx, e - 1)) 169 | } else if let Some(&[Tk::Add]) = tks.get(e.checked_sub(1)?..e) { 170 | let (y, e) = IdxExpr::from_tokens(tks, e - 1)?; 171 | let (x, e) = IdxExpr::from_tokens(tks, e)?; 172 | Some((IdxExpr::Add(Box::new(x), Box::new(y)), e)) 173 | } else if let Some(&[Tk::Sub]) = tks.get(e.checked_sub(1)?..e) { 174 | let (y, e) = IdxExpr::from_tokens(tks, e - 1)?; 175 | let (x, e) = IdxExpr::from_tokens(tks, e)?; 176 | Some((IdxExpr::Sub(Box::new(x), Box::new(y)), e)) 177 | } else if let Some(&[Tk::ScriptNumToLe64, Tk::Mul64, Tk::Num(1), Tk::Equal, Tk::Verify, Tk::Le64ToScriptNum]) = tks.get(e.checked_sub(6)?..e) { 178 | let (y, e) = IdxExpr::from_tokens(tks, e - 6)?; 179 | if let Some(&[Tk::ScriptNumToLe64]) = tks.get(e.checked_sub(1)?..e) { 180 | let (x, e) = IdxExpr::from_tokens(tks, e - 1)?; 181 | Some((IdxExpr::Mul(Box::new(x), Box::new(y)), e)) 182 | } else { 183 | None 184 | } 185 | } else if let Some(&[Tk::ScriptNumToLe64, Tk::Div64, Tk::Num(1), Tk::Equal, Tk::Verify, Tk::Nip, Tk::Le64ToScriptNum]) = tks.get(e.checked_sub(7)?..e) { 186 | let (y, e) = IdxExpr::from_tokens(tks, e - 7)?; 187 | if let Some(&[Tk::ScriptNumToLe64]) = tks.get(e.checked_sub(1)?..e) { 188 | let (x, e) = IdxExpr::from_tokens(tks, e - 1)?; 189 | Some((IdxExpr::Div(Box::new(x), Box::new(y)), e)) 190 | } else { 191 | None 192 | } 193 | } else { 194 | None 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/extensions/param.rs: -------------------------------------------------------------------------------- 1 | //! Parameters to certain covenants 2 | 3 | use std::{fmt, hash}; 4 | 5 | use elements::confidential; 6 | use elements::encode::serialize; 7 | use elements::hex::ToHex; 8 | 9 | use super::csfs::{CsfsKey, CsfsMsg}; 10 | use super::introspect_ops::Spk; 11 | use super::CovenantExt; 12 | use crate::{Error, ExtTranslator}; 13 | 14 | /// Trait for parsing extension arg from String 15 | /// Parse an argument from `s` given context of parent and argument position 16 | /// 17 | /// When parsing all allowed parameters from string, we need to restrict where 18 | /// the parameters can be allowed. For example, csfs() should not have a txout 19 | /// parameter. 20 | /// 21 | /// All parameters that should be parsed from extensions need to implement this 22 | pub trait ArgFromStr: Sized { 23 | /// Parse an argument from `s` given context of parent and argument position 24 | fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result; 25 | } 26 | 27 | /// Abstract parameter to Miniscript Extension 28 | pub trait ExtParam: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash + ArgFromStr {} 29 | 30 | impl ExtParam for T where 31 | T: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash + ArgFromStr 32 | { 33 | } 34 | 35 | impl ArgFromStr for String { 36 | fn arg_from_str(s: &str, _parent: &str, _pos: usize) -> Result { 37 | // Abstract strings are parsed without context as they don't contain any concrete 38 | // information 39 | Ok(String::from(s)) 40 | } 41 | } 42 | 43 | /// No Extensions for elements-miniscript 44 | /// All the implementations for the this function are unreachable 45 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 46 | pub enum NoExtParam {} 47 | 48 | impl fmt::Display for NoExtParam { 49 | fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | match *self {} 51 | } 52 | } 53 | 54 | impl ArgFromStr for NoExtParam { 55 | fn arg_from_str(_s: &str, _parent: &str, _pos: usize) -> Result { 56 | // This will be removed in a followup commit 57 | unreachable!("Called ArgFromStr for NoExt") 58 | } 59 | } 60 | 61 | /// All known Extension parameters/arguments 62 | #[derive(Debug, PartialEq, Eq, Clone, Hash)] 63 | pub enum CovExtArgs { 64 | /// XOnlyPublicKey (in CSFS) 65 | XOnlyKey(CsfsKey), 66 | /// Message 67 | CsfsMsg(CsfsMsg), 68 | /// Asset 69 | Asset(confidential::Asset), 70 | /// Value 71 | Value(confidential::Value), 72 | /// Script 73 | Script(Spk), 74 | } 75 | 76 | impl From for CovExtArgs { 77 | fn from(v: CsfsMsg) -> Self { 78 | Self::CsfsMsg(v) 79 | } 80 | } 81 | 82 | impl From for CovExtArgs { 83 | fn from(v: Spk) -> Self { 84 | Self::Script(v) 85 | } 86 | } 87 | 88 | impl From for CovExtArgs { 89 | fn from(v: confidential::Value) -> Self { 90 | Self::Value(v) 91 | } 92 | } 93 | 94 | impl From for CovExtArgs { 95 | fn from(v: confidential::Asset) -> Self { 96 | Self::Asset(v) 97 | } 98 | } 99 | 100 | impl From for CovExtArgs { 101 | fn from(v: CsfsKey) -> Self { 102 | Self::XOnlyKey(v) 103 | } 104 | } 105 | 106 | impl CovExtArgs { 107 | /// Creates a new csfs key variant of [`CovExtArgs`] 108 | pub fn csfs_key(key: bitcoin::key::XOnlyPublicKey) -> Self { 109 | CovExtArgs::XOnlyKey(CsfsKey(key)) 110 | } 111 | 112 | /// Creates a csfs message variant of [`CovExtArgs`] 113 | pub fn csfs_msg(msg: elements::secp256k1_zkp::Message) -> Self { 114 | CovExtArgs::CsfsMsg(CsfsMsg::new(msg.as_ref().to_vec()).expect("32 byte size message")) 115 | } 116 | 117 | /// Creates a new asset variant of [`CovExtArgs`] 118 | pub fn asset(asset: confidential::Asset) -> Self { 119 | Self::from(asset) 120 | } 121 | 122 | /// Creates a new value variant of [`CovExtArgs`] 123 | pub fn value(value: confidential::Value) -> Self { 124 | Self::from(value) 125 | } 126 | 127 | /// Creates a new script pubkey of [`CovExtArgs`] 128 | pub fn spk(spk: elements::Script) -> Self { 129 | Self::from(Spk::new(spk)) 130 | } 131 | } 132 | 133 | impl PartialOrd for CovExtArgs { 134 | fn partial_cmp(&self, other: &Self) -> Option { 135 | Some(self.cmp(other)) 136 | } 137 | } 138 | 139 | impl Ord for CovExtArgs { 140 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 141 | // HACKY implementation, need Ord/PartialOrd to make it work with other components 142 | // in the library 143 | self.to_string().cmp(&other.to_string()) 144 | } 145 | } 146 | 147 | impl fmt::Display for CovExtArgs { 148 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 149 | match self { 150 | CovExtArgs::XOnlyKey(x) => write!(f, "{}", x), 151 | CovExtArgs::CsfsMsg(m) => write!(f, "{}", m), 152 | CovExtArgs::Asset(a) => write!(f, "{}", serialize(a).to_hex()), 153 | CovExtArgs::Value(v) => write!(f, "{}", serialize(v).to_hex()), 154 | CovExtArgs::Script(s) => write!(f, "{}", s), 155 | } 156 | } 157 | } 158 | 159 | impl ArgFromStr for CovExtArgs { 160 | fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result { 161 | let arg = match (parent, pos) { 162 | ("csfs", 0) => CovExtArgs::XOnlyKey(CsfsKey::arg_from_str(s, parent, pos)?), 163 | ("csfs", 1) => CovExtArgs::CsfsMsg(CsfsMsg::arg_from_str(s, parent, pos)?), 164 | ("asset_eq", 0) | ("asset_eq", 1) | ("is_exp_asset", 0) => { 165 | CovExtArgs::Asset(confidential::Asset::arg_from_str(s, parent, pos)?) 166 | } 167 | ("value_eq", 0) | ("value_eq", 1) | ("is_exp_value", 0) => { 168 | CovExtArgs::Value(confidential::Value::arg_from_str(s, parent, pos)?) 169 | } 170 | ("spk_eq", 0) | ("spk_eq", 1) => CovExtArgs::Script(Spk::arg_from_str(s, parent, pos)?), 171 | _ => return Err(Error::Unexpected(s.to_string())), 172 | }; 173 | Ok(arg) 174 | } 175 | } 176 | 177 | /// Trait for translating different parameter types for covenant extensions 178 | pub trait ExtParamTranslator 179 | where 180 | PArg: ExtParam, 181 | QArg: ExtParam, 182 | { 183 | /// Translates one extension to another 184 | fn ext(&mut self, e: &PArg) -> Result; 185 | } 186 | 187 | // Use ExtParamTranslator as a ExTTranslator 188 | impl ExtTranslator, CovenantExt, E> for T 189 | where 190 | T: ExtParamTranslator, 191 | PArg: ExtParam, 192 | QArg: ExtParam, 193 | { 194 | /// Translates one extension to another 195 | fn ext(&mut self, cov: &CovenantExt) -> Result, E> { 196 | match *cov { 197 | CovenantExt::LegacyVerEq(ref v) => Ok(CovenantExt::LegacyVerEq(*v)), 198 | CovenantExt::LegacyOutputsPref(ref p) => Ok(CovenantExt::LegacyOutputsPref(p.clone())), 199 | CovenantExt::Csfs(ref c) => Ok(CovenantExt::Csfs(TranslateExtParam::translate_ext( 200 | c, self, 201 | )?)), 202 | CovenantExt::Arith(ref e) => Ok(CovenantExt::Arith(TranslateExtParam::translate_ext( 203 | e, self, 204 | )?)), 205 | CovenantExt::Introspect(ref c) => Ok(CovenantExt::Introspect( 206 | TranslateExtParam::translate_ext(c, self)?, 207 | )), 208 | } 209 | } 210 | } 211 | 212 | /// Converts a descriptor using abstract extension parameters to one using concrete ones, 213 | /// or vice-versa 214 | pub trait TranslateExtParam 215 | where 216 | PArg: ExtParam, 217 | QArg: ExtParam, 218 | { 219 | /// The associated output type. 220 | type Output; 221 | 222 | /// Translates a struct from one generic to another where the translations 223 | /// for Pk are provided by the given [`ExtParamTranslator`]. 224 | fn translate_ext(&self, translator: &mut T) -> Result 225 | where 226 | T: ExtParamTranslator; 227 | } 228 | -------------------------------------------------------------------------------- /src/extensions/tx_ver.rs: -------------------------------------------------------------------------------- 1 | //! Miniscript extension: ver_eq 2 | //! Note that this fragment is only supported for Segwit context 3 | //! You are most likely looking for taproot direct tx introspection 4 | 5 | use std::fmt; 6 | 7 | use elements::encode::serialize; 8 | 9 | use super::{FromTokenIterError, ParseableExt, TxEnv}; 10 | use crate::descriptor::CovError; 11 | use crate::miniscript::astelem::StackCtxOperations; 12 | use crate::miniscript::lex::{Token as Tk, TokenIter}; 13 | use crate::miniscript::satisfy::{Satisfaction, Witness}; 14 | use crate::miniscript::types::extra_props::{OpLimits, TimelockInfo}; 15 | use crate::miniscript::types::{Base, Correctness, Dissat, ExtData, Input, Malleability}; 16 | use crate::policy::{self, Liftable}; 17 | use crate::{ 18 | expression, interpreter, miniscript, util, Error, Extension, MiniscriptKey, Satisfier, 19 | ToPublicKey, 20 | }; 21 | 22 | /// Version struct 23 | /// `DEPTH <12> SUB PICK EQUAL` 24 | #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] 25 | pub struct LegacyVerEq { 26 | /// the version of transaction 27 | pub n: u32, // it's i32 in bitcoin core 28 | } 29 | 30 | impl fmt::Display for LegacyVerEq { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | write!(f, "ver_eq({})", self.n) 33 | } 34 | } 35 | 36 | impl Liftable for LegacyVerEq { 37 | fn lift(&self) -> Result, Error> { 38 | Err(Error::CovError(CovError::CovenantLift)) 39 | } 40 | } 41 | 42 | impl Extension for LegacyVerEq { 43 | fn segwit_ctx_checks(&self) -> Result<(), miniscript::context::ScriptContextError> { 44 | Ok(()) 45 | } 46 | 47 | fn corr_prop(&self) -> Correctness { 48 | Correctness { 49 | base: Base::B, 50 | input: Input::Zero, 51 | dissatisfiable: false, // No dissat from stack inputs 52 | unit: true, 53 | } 54 | } 55 | 56 | fn mall_prop(&self) -> Malleability { 57 | Malleability { 58 | dissat: Dissat::None, // No dissatisfactions from stack inputs 59 | safe: false, 60 | non_malleable: true, 61 | } 62 | } 63 | 64 | fn extra_prop(&self) -> ExtData { 65 | ExtData { 66 | pk_cost: 4 + 1 + 1 + 4, // 4 opcodes, 1 push, (5) 4 byte push 67 | has_free_verify: true, 68 | stack_elem_count_sat: Some(0), 69 | stack_elem_count_dissat: Some(0), 70 | max_sat_size: Some((0, 0)), 71 | max_dissat_size: Some((0, 0)), 72 | timelock_info: TimelockInfo::default(), 73 | exec_stack_elem_count_sat: Some(2), 74 | exec_stack_elem_count_dissat: Some(2), 75 | ops: OpLimits { 76 | count: 4, 77 | sat: Some(0), 78 | nsat: Some(0), 79 | }, 80 | } 81 | } 82 | 83 | fn script_size(&self) -> usize { 84 | 4 + 1 + 1 + 4 // opcodes + push opcodes + target size 85 | } 86 | 87 | fn from_name_tree( 88 | name: &str, 89 | children: &[expression::Tree<'_>], 90 | ) -> Result { 91 | if children.len() == 1 && name == "ver_eq" { 92 | let n = expression::terminal(&children[0], expression::parse_num) 93 | .map_err(|_| FromTokenIterError)?; 94 | Ok(Self { n }) 95 | } else { 96 | // Correct error handling while parsing fromtree 97 | Err(FromTokenIterError) 98 | } 99 | } 100 | } 101 | 102 | impl ParseableExt for LegacyVerEq { 103 | fn satisfy(&self, sat: &S) -> Satisfaction 104 | where 105 | Pk: ToPublicKey, 106 | S: Satisfier, 107 | { 108 | let wit = match sat.lookup_nversion() { 109 | Some(k) => { 110 | if k == self.n { 111 | Witness::empty() 112 | } else { 113 | Witness::Impossible 114 | } 115 | } 116 | // Note the unavailable instead of impossible because we don't know 117 | // the version 118 | None => Witness::Unavailable, 119 | }; 120 | Satisfaction { 121 | stack: wit, 122 | has_sig: false, 123 | } 124 | } 125 | 126 | fn dissatisfy(&self, sat: &S) -> Satisfaction 127 | where 128 | Pk: ToPublicKey, 129 | S: Satisfier, 130 | { 131 | let wit = if let Some(k) = sat.lookup_nversion() { 132 | if k == self.n { 133 | Witness::Impossible 134 | } else { 135 | Witness::empty() 136 | } 137 | } else { 138 | Witness::empty() 139 | }; 140 | Satisfaction { 141 | stack: wit, 142 | has_sig: false, 143 | } 144 | } 145 | 146 | fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder { 147 | builder.check_item_eq(12, &serialize(&self.n)) 148 | } 149 | 150 | fn from_token_iter(tokens: &mut TokenIter<'_>) -> Result { 151 | let ver = { 152 | let sl = tokens.peek_slice(5).ok_or(FromTokenIterError)?; 153 | if let Tk::PickPush4(ver) = sl[3] { 154 | if sl[0] == Tk::Depth 155 | && sl[1] == Tk::Num(12) 156 | && sl[2] == Tk::Sub 157 | && sl[4] == Tk::Equal 158 | { 159 | Self { n: ver } 160 | } else { 161 | return Err(FromTokenIterError); 162 | } 163 | } else { 164 | return Err(FromTokenIterError); 165 | } 166 | }; 167 | tokens.advance(5).expect("Size checked previously"); 168 | Ok(ver) 169 | } 170 | 171 | fn evaluate( 172 | &self, 173 | stack: &mut interpreter::Stack, 174 | _txenv: Option<&TxEnv>, 175 | ) -> Result { 176 | // Version is at index 11 177 | let ver = stack[11]; 178 | let elem = ver.try_push()?; 179 | if elem.len() == 4 { 180 | let wit_ver = util::slice_to_u32_le(elem); 181 | if wit_ver == self.n { 182 | stack.push(interpreter::Element::Satisfied); 183 | Ok(true) 184 | } else { 185 | Ok(false) 186 | } 187 | } else { 188 | Err(interpreter::Error::CovWitnessSizeErr { 189 | pos: 1, 190 | expected: 4, 191 | actual: elem.len(), 192 | }) 193 | } 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use bitcoin::PublicKey; 200 | 201 | use super::*; 202 | use crate::{Miniscript, Segwitv0}; 203 | 204 | #[test] 205 | fn test_ver_eq() { 206 | type MsExtVer = Miniscript; 207 | 208 | let ms = MsExtVer::from_str_insane("ver_eq(8)").unwrap(); 209 | // test string rtt 210 | assert_eq!(ms.to_string(), "ver_eq(8)"); 211 | // script rtt 212 | assert_eq!(ms, MsExtVer::parse_insane(&ms.encode()).unwrap()) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /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!("elwsh(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 | /// Macro for implementing FromTree trait. This avoids copying all the Pk::Associated type bounds 22 | /// throughout the codebase. 23 | macro_rules! impl_from_tree { 24 | ($(;$gen:ident; $gen_con:ident, )* 25 | $name: ty, 26 | $(=> $ext:ident; $trt:ident, )? 27 | $(#[$meta:meta])* 28 | fn $fn:ident ( $($arg:ident : $type:ty),* ) -> $ret:ty 29 | $body:block 30 | ) => { 31 | impl $crate::expression::FromTree for $name 32 | where 33 | Pk: bitcoin_miniscript::FromStrKey, 34 | $($gen : $gen_con,)* 35 | $($ext: $trt)? 36 | { 37 | 38 | $(#[$meta])* 39 | fn $fn($($arg: $type)* ) -> $ret { 40 | $body 41 | } 42 | } 43 | }; 44 | } 45 | 46 | /// Macro for implementing FromStr trait. This avoids copying all the Pk::Associated type bounds 47 | /// throughout the codebase. 48 | macro_rules! impl_from_str { 49 | ($(;$gen:ident; $gen_con:ident, )* $name: ty, 50 | $(=> $ext:ident; $trt:ident, )? 51 | type Err = $err_ty:ty;, 52 | $(#[$meta:meta])* 53 | fn $fn:ident ( $($arg:ident : $type:ty),* ) -> $ret:ty 54 | $body:block 55 | ) => { 56 | impl core::str::FromStr for $name 57 | where 58 | Pk: bitcoin_miniscript::FromStrKey, 59 | $($gen : $gen_con,)* 60 | $($ext: $trt)? 61 | { 62 | type Err = $err_ty; 63 | 64 | $(#[$meta])* 65 | fn $fn($($arg: $type)* ) -> $ret { 66 | $body 67 | } 68 | } 69 | }; 70 | } 71 | 72 | /// Macro for impl Struct with associated bounds. This avoids copying all the Pk::Associated type bounds 73 | /// throughout the codebase. 74 | macro_rules! impl_block_str { 75 | ($(;$gen:ident; $gen_con:ident, )* $name: ty, 76 | $(=> $ext:ident; $trt:ident, )? 77 | $(#[$meta:meta])* 78 | $v:vis fn $fn:ident ( $($arg:ident : $type:ty, )* ) -> $ret:ty 79 | $body:block 80 | ) => { 81 | impl $name 82 | where 83 | Pk: bitcoin_miniscript::FromStrKey, 84 | $($gen : $gen_con,)* 85 | $($ext: $trt)? 86 | { 87 | $(#[$meta])* 88 | $v fn $fn($($arg: $type,)* ) -> $ret { 89 | $body 90 | } 91 | } 92 | }; 93 | } 94 | 95 | /// A macro that implements serde serialization and deserialization using the 96 | /// `fmt::Display` and `str::FromStr` traits. 97 | macro_rules! serde_string_impl_pk { 98 | ($name:ident, $expecting:expr $(, $gen:ident; $gen_con:ident)* $(=> $ext:ident ; $ext_bound:ident)*) => { 99 | #[cfg(feature = "serde")] 100 | impl<'de, Pk $(, $gen)* $(, $ext)*> $crate::serde::Deserialize<'de> for $name 101 | where 102 | Pk: bitcoin_miniscript::FromStrKey, 103 | $($gen : $gen_con,)* 104 | $($ext : $ext_bound,)* 105 | { 106 | fn deserialize(deserializer: D) -> Result<$name, D::Error> 107 | where 108 | D: $crate::serde::de::Deserializer<'de>, 109 | { 110 | use std::fmt::{self, Formatter}; 111 | use std::marker::PhantomData; 112 | use std::str::FromStr; 113 | 114 | #[allow(unused_parens)] 115 | struct Visitor(PhantomData<(Pk $(, $gen)* $(, $ext)*)>); 116 | impl<'de, Pk $(, $gen)* $(, $ext)*> $crate::serde::de::Visitor<'de> for Visitor 117 | where 118 | Pk: bitcoin_miniscript::FromStrKey, 119 | $($gen: $gen_con,)* 120 | $($ext : $ext_bound,)* 121 | { 122 | type Value = $name; 123 | 124 | fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 125 | formatter.write_str($expecting) 126 | } 127 | 128 | fn visit_str(self, v: &str) -> Result 129 | where 130 | E: $crate::serde::de::Error, 131 | { 132 | $name::from_str(v).map_err(E::custom) 133 | } 134 | 135 | fn visit_borrowed_str(self, v: &'de str) -> Result 136 | where 137 | E: $crate::serde::de::Error, 138 | { 139 | self.visit_str(v) 140 | } 141 | 142 | fn visit_string(self, v: String) -> Result 143 | where 144 | E: $crate::serde::de::Error, 145 | { 146 | self.visit_str(&v) 147 | } 148 | } 149 | 150 | deserializer.deserialize_str(Visitor(PhantomData)) 151 | } 152 | } 153 | 154 | #[cfg(feature = "serde")] 155 | impl<'de, Pk $(, $gen)* $(, $ext)*> $crate::serde::Serialize for $name 156 | where 157 | Pk: MiniscriptKey, 158 | $($gen: $gen_con,)* 159 | $($ext : $ext_bound,)* 160 | { 161 | fn serialize(&self, serializer: S) -> Result 162 | where 163 | S: $crate::serde::Serializer, 164 | { 165 | serializer.collect_str(&self) 166 | } 167 | } 168 | }; 169 | } 170 | 171 | macro_rules! match_token { 172 | // Base case 173 | ($tokens:expr => $sub:expr,) => { $sub }; 174 | // Recursive case 175 | ($tokens:expr, $($first:pat $(,$rest:pat)* => $sub:expr,)*) => { 176 | match $tokens.next() { 177 | $( 178 | Some($first) => match_token!($tokens $(,$rest)* => $sub,), 179 | )* 180 | Some(other) => return Err(Error::Unexpected(other.to_string())), 181 | None => return Err(Error::UnexpectedStart), 182 | } 183 | }; 184 | } 185 | -------------------------------------------------------------------------------- /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 Initial witness size allowed 27 | /// `` 28 | pub const MAX_STANDARD_P2WSH_STACK_ITEM_SIZE: usize = 80; 29 | /// Maximum items during stack execution 30 | // This limits also applies for initial stack satisfaction 31 | // https://github.com/bitcoin/bitcoin/blob/3af495d6972379b07530a5fcc2665aa626d01621/src/script/script.h#L35 32 | pub const MAX_STACK_SIZE: usize = 1000; 33 | /** The maximum allowed weight for a block, see BIP 141 (network rule) */ 34 | pub const MAX_BLOCK_WEIGHT: usize = 4000000; 35 | 36 | /// Maximum pubkeys as arguments to CHECKMULTISIG 37 | // https://github.com/bitcoin/bitcoin/blob/6acda4b00b3fc1bfac02f5de590e1a5386cbc779/src/script/script.h#L30 38 | pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20; 39 | -------------------------------------------------------------------------------- /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 elements_miniscript as miniscript; 14 | /// use miniscript::{bitcoin::PublicKey, policy::concrete::Policy, Translator, hash256}; 15 | /// use std::str::FromStr; 16 | /// use miniscript::translate_hash_fail; 17 | /// use std::collections::HashMap; 18 | /// use miniscript::bitcoin::hashes::{sha256, hash160, ripemd160}; 19 | /// let alice_key = "0270cf3c71f65a3d93d285d9149fddeeb638f87a2d4d8cf16c525f71c417439777"; 20 | /// let bob_key = "02f43b15c50a436f5335dbea8a64dd3b4e63e34c3b50c42598acb5f4f336b5d2fb"; 21 | /// let placeholder_policy = Policy::::from_str("and(pk(alice_key),pk(bob_key))").unwrap(); 22 | /// 23 | /// // Information to translator abstract String type keys to concrete bitcoin::PublicKey. 24 | /// // In practice, wallets would map from String key names to BIP32 keys 25 | /// struct StrPkTranslator { 26 | /// pk_map: HashMap 27 | /// } 28 | /// 29 | /// // If we also wanted to provide mapping of other associated types(sha256, older etc), 30 | /// // we would use the general Translator Trait. 31 | /// impl Translator for StrPkTranslator { 32 | /// // Provides the translation public keys P -> Q 33 | /// fn pk(&mut self, pk: &String) -> Result { 34 | /// self.pk_map.get(pk).copied().ok_or(()) // Dummy Err 35 | /// } 36 | /// 37 | /// // Fail for hash types 38 | /// translate_hash_fail!(String, bitcoin::PublicKey, ()); 39 | /// } 40 | /// 41 | /// let mut pk_map = HashMap::new(); 42 | /// pk_map.insert(String::from("alice_key"), bitcoin::PublicKey::from_str(alice_key).unwrap()); 43 | /// pk_map.insert(String::from("bob_key"), bitcoin::PublicKey::from_str(bob_key).unwrap()); 44 | /// let mut t = StrPkTranslator { pk_map: pk_map }; 45 | /// ``` 46 | #[macro_export] 47 | macro_rules! translate_hash_fail { 48 | ($source: ty, $target:ty, $error_ty: ty) => { 49 | fn sha256( 50 | &mut self, 51 | _sha256: &<$source as $crate::MiniscriptKey>::Sha256, 52 | ) -> Result<<$target as $crate::MiniscriptKey>::Sha256, $error_ty> { 53 | panic!("Called sha256 on translate_only_pk") 54 | } 55 | 56 | fn hash256( 57 | &mut self, 58 | _hash256: &<$source as $crate::MiniscriptKey>::Hash256, 59 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash256, $error_ty> { 60 | panic!("Called hash256 on translate_only_pk") 61 | } 62 | 63 | fn hash160( 64 | &mut self, 65 | _hash160: &<$source as $crate::MiniscriptKey>::Hash160, 66 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash160, $error_ty> { 67 | panic!("Called hash160 on translate_only_pk") 68 | } 69 | 70 | fn ripemd160( 71 | &mut self, 72 | _ripemd160: &<$source as $crate::MiniscriptKey>::Ripemd160, 73 | ) -> Result<<$target as $crate::MiniscriptKey>::Ripemd160, $error_ty> { 74 | panic!("Called ripemd160 on translate_only_pk") 75 | } 76 | }; 77 | } 78 | 79 | /// Macro for translation of associated types where the associated type is the same 80 | /// Handy for Derived -> concrete keys where the associated types are the same. 81 | /// 82 | /// Writing the complete translator trait is tedious. This macro is handy when 83 | /// we are not trying the associated types for hash160, ripemd160, hash256 and 84 | /// sha256. 85 | /// 86 | /// See also [`crate::translate_hash_fail`] 87 | #[macro_export] 88 | macro_rules! translate_hash_clone { 89 | ($source: ty, $target:ty, $error_ty: ty) => { 90 | fn sha256( 91 | &mut self, 92 | sha256: &<$source as $crate::MiniscriptKey>::Sha256, 93 | ) -> Result<<$target as $crate::MiniscriptKey>::Sha256, $error_ty> { 94 | Ok((*sha256).into()) 95 | } 96 | 97 | fn hash256( 98 | &mut self, 99 | hash256: &<$source as $crate::MiniscriptKey>::Hash256, 100 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash256, $error_ty> { 101 | Ok((*hash256).into()) 102 | } 103 | 104 | fn hash160( 105 | &mut self, 106 | hash160: &<$source as $crate::MiniscriptKey>::Hash160, 107 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash160, $error_ty> { 108 | Ok((*hash160).into()) 109 | } 110 | 111 | fn ripemd160( 112 | &mut self, 113 | ripemd160: &<$source as $crate::MiniscriptKey>::Ripemd160, 114 | ) -> Result<<$target as $crate::MiniscriptKey>::Ripemd160, $error_ty> { 115 | Ok((*ripemd160).into()) 116 | } 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /src/simplicity.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | use std::fmt; 3 | use std::marker::PhantomData; 4 | use std::str::FromStr; 5 | use std::sync::Arc; 6 | 7 | use bitcoin_miniscript::ToPublicKey; 8 | use elements::{LockTime, SchnorrSig, Sequence}; 9 | use elements::taproot::TapLeafHash; 10 | use simplicity::{Policy, FailEntropy, Preimage32}; 11 | 12 | use crate::policy::concrete::PolicyError; 13 | use crate::{expression, Error, MiniscriptKey}; 14 | 15 | impl_from_tree!( 16 | Policy, 17 | fn from_tree(top: &expression::Tree) -> Result { 18 | match (top.name, top.args.len() as u32) { 19 | ("UNSATISFIABLE", 0) => Ok(Policy::Unsatisfiable(FailEntropy::ZERO)), 20 | ("TRIVIAL", 0) => Ok(Policy::Trivial), 21 | ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), 22 | ("after", 1) => expression::terminal(&top.args[0], |x| { 23 | expression::parse_num(x).map(Policy::After) 24 | }), 25 | ("older", 1) => expression::terminal(&top.args[0], |x| { 26 | expression::parse_num(x).map(Policy::Older) 27 | }), 28 | ("sha256", 1) => expression::terminal(&top.args[0], |x| { 29 | Pk::Sha256::from_str(x).map(Policy::Sha256) 30 | }), 31 | ("and", _) => { 32 | if top.args.len() != 2 { 33 | return Err(Error::PolicyError(PolicyError::NonBinaryArgAnd)); 34 | } 35 | let left = Arc::new(Policy::from_tree(&top.args[0])?); 36 | let right = Arc::new(Policy::from_tree(&top.args[0])?); 37 | Ok(Policy::And { left, right }) 38 | } 39 | ("or", _) => { 40 | if top.args.len() != 2 { 41 | return Err(Error::PolicyError(PolicyError::NonBinaryArgOr)); 42 | } 43 | let left = Arc::new(Policy::from_tree(&top.args[0])?); 44 | let right = Arc::new(Policy::from_tree(&top.args[0])?); 45 | Ok(Policy::Or { left, right }) 46 | } 47 | ("thresh", nsubs) => { 48 | if nsubs == 0 { 49 | return Err(Error::Unexpected("thresh without args".to_owned())); 50 | } 51 | if nsubs < 3 { 52 | return Err(Error::Unexpected( 53 | "thresh must have a threshold value and at least 2 children".to_owned(), 54 | )); 55 | } 56 | if !top.args[0].args.is_empty() { 57 | return Err(Error::Unexpected(top.args[0].args[0].name.to_owned())); 58 | } 59 | 60 | let thresh: u32 = expression::parse_num(top.args[0].name)?; 61 | if thresh >= nsubs { 62 | return Err(Error::Unexpected(top.args[0].name.to_owned())); 63 | } 64 | 65 | let mut subs = Vec::with_capacity(top.args.len() - 1); 66 | for arg in &top.args[1..] { 67 | subs.push(Policy::from_tree(arg)?); 68 | } 69 | Ok(Policy::Threshold(thresh as usize, subs)) 70 | } 71 | _ => Err(Error::Unexpected(top.name.to_owned())), 72 | } 73 | } 74 | ); 75 | 76 | // We cannot implement FromStr for Policy because neither is defined in this crate 77 | // Use a crate-local wrapper type to avoid code repetition 78 | // Users use `Tr` / `Descriptor` and never encounter this wrapper 79 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)] 80 | pub(crate) struct PolicyWrapper(pub Policy); 81 | 82 | impl fmt::Debug for PolicyWrapper { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | fmt::Debug::fmt(&self.0, f) 85 | } 86 | } 87 | 88 | impl_from_str!( 89 | PolicyWrapper, 90 | type Err = Error;, 91 | fn from_str(s: &str) -> Result { 92 | let tree = expression::Tree::from_str(s)?; 93 | as expression::FromTree>::from_tree(&tree).map(PolicyWrapper) 94 | } 95 | ); 96 | 97 | // We cannot implement ForEachKey for Policy because it is not defined in this crate 98 | // We cannot use our wrapper because we don't own the Policy (we have a reference) 99 | // Implementing a wrapper of Cow<'a, Policy> leads to lifetime issues 100 | // when implementing ForEachKey, because for_each_key() has its own lifetime 'a 101 | pub fn for_each_key<'a, Pk: MiniscriptKey + 'a, F: FnMut(&'a Pk) -> bool>(policy: &'a Policy, mut pred: F) -> bool 102 | { 103 | let mut stack = vec![policy]; 104 | 105 | while let Some(top) = stack.pop() { 106 | match top { 107 | Policy::Key(key) => { 108 | if !pred(key) { 109 | return false; 110 | } 111 | } 112 | Policy::And { left, right } | Policy::Or { left, right } => { 113 | stack.push(right); 114 | stack.push(left); 115 | } 116 | Policy::Threshold(_, sub_policies) => { 117 | stack.extend(sub_policies.iter()); 118 | } 119 | _ => {} 120 | } 121 | } 122 | 123 | true 124 | } 125 | 126 | // We could make crate::Satisfier a subtrait of simplicity::Satisfier, 127 | // but then we would have to implement simplicity::Satisfier for all the blanket implementations 128 | // of crate::Satisfier, such as HashMap, which is annoying 129 | // We might choose to do so in the future, but for now a crate-local wrapper is easier 130 | // This wrapper is internally used by `Tr` and is never encountered by users 131 | pub(crate) struct SatisfierWrapper>(S, PhantomData); 132 | 133 | impl> SatisfierWrapper { 134 | pub fn new(satisfier: S) -> Self { 135 | Self(satisfier, PhantomData) 136 | } 137 | } 138 | 139 | impl> simplicity::Satisfier for SatisfierWrapper { 140 | fn lookup_tap_leaf_script_sig(&self, pk: &Pk, hash: &TapLeafHash) -> Option { 141 | self.0.lookup_tap_leaf_script_sig(pk, hash) 142 | } 143 | 144 | fn lookup_sha256(&self, hash: &Pk::Sha256) -> Option { 145 | self.0.lookup_sha256(hash) 146 | } 147 | 148 | fn check_older(&self, sequence: Sequence) -> bool { 149 | self.0.check_older(sequence) 150 | } 151 | 152 | fn check_after(&self, locktime: LockTime) -> bool { 153 | self.0.check_after(locktime) 154 | } 155 | } 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use secp256k1::XOnlyPublicKey; 160 | use crate::DescriptorPublicKey; 161 | use super::*; 162 | 163 | #[test] 164 | fn parse_bad_thresh() { 165 | assert_eq!( 166 | PolicyWrapper::::from_str("thresh()"), 167 | Err(Error::Unexpected( 168 | "thresh must have a threshold value and at least 2 children".to_string() 169 | )), 170 | ); 171 | 172 | assert_eq!( 173 | PolicyWrapper::::from_str("thresh"), 174 | Err(Error::Unexpected("thresh without args".to_string())), 175 | ); 176 | 177 | assert_eq!( 178 | PolicyWrapper::::from_str("thresh(0)"), 179 | Err(Error::Unexpected( 180 | "thresh must have a threshold value and at least 2 children".to_string() 181 | )), 182 | ); 183 | 184 | assert_eq!( 185 | PolicyWrapper::::from_str("thresh(0,TRIVIAL)"), 186 | Err(Error::Unexpected( 187 | "thresh must have a threshold value and at least 2 children".to_string() 188 | )), 189 | ); 190 | 191 | assert!(PolicyWrapper::::from_str("thresh(0,TRIVIAL,TRIVIAL)").is_ok()); 192 | assert!(PolicyWrapper::::from_str("thresh(2,TRIVIAL,TRIVIAL)").is_ok()); 193 | 194 | assert_eq!( 195 | PolicyWrapper::::from_str("thresh(3,TRIVIAL,TRIVIAL)"), 196 | Err(Error::Unexpected("3".to_string())), 197 | ); 198 | } 199 | 200 | #[test] 201 | fn decode_xpub() { 202 | let s = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*"; 203 | let decoded_key = DescriptorPublicKey::from_str(s).expect("constant key"); 204 | let s = format!("pk({})", s); 205 | let decoded_policy = PolicyWrapper::::from_str(&s).expect("decode policy").0; 206 | 207 | if let Policy::Key(key) = decoded_policy { 208 | assert_eq!(decoded_key, key); 209 | } else { 210 | panic!("Decoded policy should be public key") 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Generally useful utilities for test scripts 4 | 5 | use std::collections::HashMap; 6 | use std::str::FromStr; 7 | 8 | use bitcoin::hashes::{hash160, ripemd160, sha256}; 9 | use bitcoin::secp256k1; 10 | 11 | use crate::extensions::param::ExtParamTranslator; 12 | use crate::extensions::CovExtArgs; 13 | use crate::miniscript::context::SigType; 14 | use crate::{hash256, ToPublicKey, Translator}; 15 | 16 | /// Translate from a String MiniscriptKey type to bitcoin::PublicKey 17 | /// If the hashmap is populated, this will lookup for keys in HashMap 18 | /// Otherwise, this will return a translation to a random key 19 | #[derive(Debug, PartialEq, Eq, Clone)] 20 | pub struct StrKeyTranslator { 21 | pub pk_map: HashMap, 22 | pub pkh_map: HashMap, 23 | pub sha256_map: HashMap, 24 | pub ripemd160_map: HashMap, 25 | pub hash160_map: HashMap, 26 | } 27 | 28 | impl Translator for StrKeyTranslator { 29 | fn pk(&mut self, pk: &String) -> Result { 30 | let key = self.pk_map.get(pk).copied().unwrap_or_else(|| { 31 | bitcoin::PublicKey::from_str( 32 | "02c2122e30e73f7fe37986e3f81ded00158e94b7ad472369b83bbdd28a9a198a39", 33 | ) 34 | .unwrap() 35 | }); 36 | Ok(key) 37 | } 38 | 39 | fn sha256(&mut self, _sha256: &String) -> Result { 40 | let hash = sha256::Hash::from_str( 41 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 42 | ) 43 | .unwrap(); 44 | Ok(hash) 45 | } 46 | 47 | fn hash256(&mut self, _hash256: &String) -> Result { 48 | // hard coded value 49 | let hash = hash256::Hash::from_str( 50 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 51 | ) 52 | .unwrap(); 53 | Ok(hash) 54 | } 55 | 56 | fn ripemd160(&mut self, _ripemd160: &String) -> Result { 57 | let hash = ripemd160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 58 | Ok(hash) 59 | } 60 | 61 | fn hash160(&mut self, _hash160: &String) -> Result { 62 | let hash = hash160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 63 | Ok(hash) 64 | } 65 | } 66 | 67 | /// Same as [`StrKeyTranslator`], but for [`bitcoin::key::XOnlyPublicKey`] 68 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 69 | pub struct StrXOnlyKeyTranslator { 70 | pub pk_map: HashMap, 71 | pub pkh_map: HashMap, 72 | pub sha256_map: HashMap, 73 | pub ripemd160_map: HashMap, 74 | pub hash160_map: HashMap, 75 | } 76 | 77 | impl Translator for StrXOnlyKeyTranslator { 78 | fn pk(&mut self, pk: &String) -> Result { 79 | let key = self.pk_map.get(pk).copied().unwrap_or_else(|| { 80 | bitcoin::key::XOnlyPublicKey::from_str( 81 | "c2122e30e73f7fe37986e3f81ded00158e94b7ad472369b83bbdd28a9a198a39", 82 | ) 83 | .unwrap() 84 | }); 85 | Ok(key) 86 | } 87 | 88 | fn sha256(&mut self, _sha256: &String) -> Result { 89 | let hash = sha256::Hash::from_str( 90 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 91 | ) 92 | .unwrap(); 93 | Ok(hash) 94 | } 95 | 96 | fn hash256(&mut self, _hash256: &String) -> Result { 97 | let hash = hash256::Hash::from_str( 98 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 99 | ) 100 | .unwrap(); 101 | Ok(hash) 102 | } 103 | 104 | fn ripemd160(&mut self, _ripemd160: &String) -> Result { 105 | let hash = ripemd160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 106 | Ok(hash) 107 | } 108 | 109 | fn hash160(&mut self, _hash160: &String) -> Result { 110 | let hash = hash160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 111 | Ok(hash) 112 | } 113 | } 114 | 115 | // Deterministically sample keys to allow reproducible tests 116 | fn random_sks(n: usize) -> Vec { 117 | let mut sk = [0; 32]; 118 | let mut sks = vec![]; 119 | for i in 1..n + 1 { 120 | sk[0] = i as u8; 121 | sk[1] = (i >> 8) as u8; 122 | sk[2] = (i >> 16) as u8; 123 | sk[3] = (i >> 24) as u8; 124 | 125 | let sk = secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key"); 126 | sks.push(sk) 127 | } 128 | sks 129 | } 130 | 131 | impl StrKeyTranslator { 132 | pub fn new() -> Self { 133 | let secp = secp256k1::Secp256k1::new(); 134 | let sks = random_sks(26); 135 | let pks: Vec<_> = sks 136 | .iter() 137 | .map(|sk| bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, sk))) 138 | .collect(); 139 | let mut pk_map = HashMap::new(); 140 | let mut pkh_map = HashMap::new(); 141 | for (i, c) in (b'A'..=b'Z').enumerate() { 142 | let key = String::from_utf8(vec![c]).unwrap(); 143 | pk_map.insert(key.clone(), pks[i]); 144 | pkh_map.insert(key, pks[i].to_pubkeyhash(SigType::Ecdsa)); 145 | } 146 | // We don't bother filling in sha256_map preimages in default implementation as it not unnecessary 147 | // for sane miniscripts 148 | Self { 149 | pk_map, 150 | pkh_map, 151 | sha256_map: HashMap::new(), 152 | ripemd160_map: HashMap::new(), 153 | hash160_map: HashMap::new(), 154 | } 155 | } 156 | } 157 | 158 | impl StrXOnlyKeyTranslator { 159 | pub fn new() -> Self { 160 | let secp = secp256k1::Secp256k1::new(); 161 | let sks = random_sks(26); 162 | let pks: Vec<_> = sks 163 | .iter() 164 | .map(|sk| { 165 | let keypair = secp256k1::Keypair::from_secret_key(&secp, sk); 166 | let (pk, _parity) = bitcoin::key::XOnlyPublicKey::from_keypair(&keypair); 167 | pk 168 | }) 169 | .collect(); 170 | let mut pk_map = HashMap::new(); 171 | let mut pkh_map = HashMap::new(); 172 | for (i, c) in (b'A'..=b'Z').enumerate() { 173 | let key = String::from_utf8(vec![c]).unwrap(); 174 | pk_map.insert(key.clone(), pks[i]); 175 | pkh_map.insert(key, pks[i].to_pubkeyhash(SigType::Schnorr)); 176 | } 177 | // We don't bother filling in sha256_map preimages in default implementation as it not unnecessary 178 | // for sane miniscripts 179 | Self { 180 | pk_map, 181 | pkh_map, 182 | sha256_map: HashMap::new(), 183 | ripemd160_map: HashMap::new(), 184 | hash160_map: HashMap::new(), 185 | } 186 | } 187 | } 188 | 189 | /// Translate Abstract Str to Consensus Extensions 190 | #[derive(Debug, PartialEq, Eq, Clone, Default)] 191 | pub struct StrExtTranslator { 192 | pub ext_map: HashMap, 193 | } 194 | 195 | impl ExtParamTranslator for StrExtTranslator { 196 | fn ext(&mut self, e: &String) -> Result { 197 | let x = self.ext_map.get(e).expect("Ext Mapping not found"); 198 | Ok(x.clone()) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | use bitcoin::hashes::Hash; 3 | use elements::{self, opcodes, script, PubkeyHash, Script}; 4 | 5 | use crate::miniscript::context; 6 | use crate::{ScriptContext, ToPublicKey}; 7 | 8 | pub(crate) fn varint_len(n: usize) -> usize { 9 | bitcoin::VarInt(n as u64).size() 10 | } 11 | 12 | // Helper function to calculate witness size 13 | pub(crate) fn witness_size(wit: &[Vec]) -> usize { 14 | wit.iter().map(Vec::len).sum::() + varint_len(wit.len()) 15 | } 16 | 17 | pub(crate) fn witness_to_scriptsig(witness: &[Vec]) -> Script { 18 | let mut b = script::Builder::new(); 19 | for wit in witness { 20 | if let Ok(n) = script::read_scriptint(wit) { 21 | b = b.push_int(n); 22 | } else { 23 | b = b.push_slice(wit); 24 | } 25 | } 26 | b.into_script() 27 | } 28 | 29 | macro_rules! define_slice_to_le { 30 | ($name: ident, $type: ty) => { 31 | #[inline] 32 | pub(crate) fn $name(slice: &[u8]) -> $type { 33 | assert_eq!(slice.len(), ::std::mem::size_of::<$type>()); 34 | let mut res = 0; 35 | for i in 0..::std::mem::size_of::<$type>() { 36 | res |= (slice[i] as $type) << i * 8; 37 | } 38 | res 39 | } 40 | }; 41 | } 42 | 43 | define_slice_to_le!(slice_to_u32_le, u32); 44 | 45 | /// Helper to encode an integer in script format 46 | /// Copied from rust-bitcoin 47 | pub(crate) fn build_scriptint(n: i64) -> Vec { 48 | if n == 0 { 49 | return vec![]; 50 | } 51 | 52 | let neg = n < 0; 53 | 54 | let mut abs = if neg { -n } else { n } as usize; 55 | let mut v = vec![]; 56 | while abs > 0xFF { 57 | v.push((abs & 0xFF) as u8); 58 | abs >>= 8; 59 | } 60 | // If the number's value causes the sign bit to be set, we need an extra 61 | // byte to get the correct value and correct sign bit 62 | if abs & 0x80 != 0 { 63 | v.push(abs as u8); 64 | v.push(if neg { 0x80u8 } else { 0u8 }); 65 | } 66 | // Otherwise we just set the sign bit ourselves 67 | else { 68 | abs |= if neg { 0x80 } else { 0 }; 69 | v.push(abs as u8); 70 | } 71 | v 72 | } 73 | /// Get the count of non-push opcodes 74 | // Export to upstream 75 | #[cfg(test)] 76 | pub(crate) fn count_non_push_opcodes(script: &Script) -> Result { 77 | let mut count = 0; 78 | for ins in script.instructions() { 79 | if let script::Instruction::Op(..) = ins? { 80 | count += 1; 81 | } 82 | } 83 | Ok(count) 84 | } 85 | // trait for pushing key that depend on context 86 | pub(crate) trait MsKeyBuilder { 87 | /// Serialize the key as bytes based on script context. Used when encoding miniscript into bitcoin script 88 | fn push_ms_key(self, key: &Pk) -> Self 89 | where 90 | Pk: ToPublicKey, 91 | Ctx: ScriptContext; 92 | 93 | /// Serialize the key hash as bytes based on script context. Used when encoding miniscript into bitcoin script 94 | fn push_ms_key_hash(self, key: &Pk) -> Self 95 | where 96 | Pk: ToPublicKey, 97 | Ctx: ScriptContext; 98 | } 99 | 100 | impl MsKeyBuilder for script::Builder { 101 | fn push_ms_key(self, key: &Pk) -> Self 102 | where 103 | Pk: ToPublicKey, 104 | Ctx: ScriptContext, 105 | { 106 | match Ctx::sig_type() { 107 | context::SigType::Ecdsa => self.push_key(&key.to_public_key()), 108 | context::SigType::Schnorr => self.push_slice(&key.to_x_only_pubkey().serialize()), 109 | } 110 | } 111 | 112 | fn push_ms_key_hash(self, key: &Pk) -> Self 113 | where 114 | Pk: ToPublicKey, 115 | Ctx: ScriptContext, 116 | { 117 | match Ctx::sig_type() { 118 | context::SigType::Ecdsa => self.push_slice(key.to_public_key().pubkey_hash().as_ref()), 119 | context::SigType::Schnorr => { 120 | self.push_slice(PubkeyHash::hash(&key.to_x_only_pubkey().serialize()).as_ref()) 121 | } 122 | } 123 | } 124 | } 125 | 126 | /// Checks whether a script pubkey is a P2TR output. 127 | #[inline] 128 | pub fn is_v1_p2tr(script: &Script) -> bool { 129 | script.len() == 34 130 | && script[0] == opcodes::all::OP_PUSHNUM_1.into_u8() 131 | && script[1] == opcodes::all::OP_PUSHBYTES_32.into_u8() 132 | } 133 | --------------------------------------------------------------------------------