Skip to content

Commit d3af6d9

Browse files
committed
Merge #569: Update MSRV to 1.48. Fixes CI
6b76997 Use a static map to lookup which characters are allowed (sanket1729) 60fde9e Check that we only accept INPUT_CHARSET in from_str (sanket1729) a396605 Fix taproot internal key parsing (sanket1729) 68c7c49 clippy warnings (sanket1729) b238366 Update MSRV to 1.48 (sanket1729) Pull request description: ACKs for top commit: apoelstra: ACK 6b76997 Tree-SHA512: 5b42b3a1d8156c5a7aa5924bfbc5d79f8196173b4f9a2b502480edbfa4d2f9e2cd74dd34839de76e12a74ad0ccfc335f816890db1b4b6705bdbb2e7bc81fcebc
2 parents aa1769c + 6b76997 commit d3af6d9

27 files changed

+170
-124
lines changed

.github/workflows/fuzz.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ compile_descriptor,
3939
key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
4040
- uses: actions-rs/toolchain@v1
4141
with:
42-
toolchain: 1.58
42+
toolchain: 1.64
4343
override: true
4444
profile: minimal
4545
- name: fuzz

.github/workflows/rust.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ jobs:
5454
- rust: stable
5555
- rust: beta
5656
- rust: nightly
57-
- rust: 1.41.1
58-
- rust: 1.47
57+
- rust: 1.48
5958
steps:
6059
- name: Checkout Crate
6160
uses: actions/checkout@v2

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
![Build](https://github.com/rust-bitcoin/rust-miniscript/workflows/Continuous%20integration/badge.svg)
22

3-
**Minimum Supported Rust Version:** 1.41.1
3+
**Minimum Supported Rust Version:** 1.48.0
44

55
# Miniscript
66

@@ -40,8 +40,7 @@ The cargo feature `std` is enabled by default. At least one of the features `std
4040
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.
4141

4242
## Minimum Supported Rust Version (MSRV)
43-
This library should always compile with any combination of features (minus
44-
`no-std`) on **Rust 1.41.1** or **Rust 1.47** with `no-std`.
43+
This library should always compile with any combination of features on **Rust 1.48.0**.
4544

4645
Some dependencies do not play nicely with our MSRV, if you are running the tests
4746
you may need to pin some dependencies. See `./contrib/test.sh` for current pinning.

bitcoind-tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ publish = false
99

1010
[dependencies]
1111
miniscript = {path = "../"}
12-
bitcoind = { version = "0.30.0" }
12+
bitcoind = { version = "0.32.0" }
1313
actual-rand = { package = "rand", version = "0.8.4"}
1414
secp256k1 = {version = "0.27.0", features = ["rand-std"]}
1515
internals = { package = "bitcoin-private", version = "0.1.0", default_features = false }

clippy.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
msrv = "1.41.1"
1+
msrv = "1.48.0"

contrib/test.sh

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,14 @@ then
1414
cargo fmt -- --check
1515
fi
1616

17-
# Pin dependencies required to build with Rust 1.41.1
18-
if cargo --version | grep "1\.41\.0"; then
17+
# Pin dependencies required to build with Rust 1.48.0
18+
if cargo --version | grep "1\.48\.0"; then
1919
cargo update -p once_cell --precise 1.13.1
20+
cargo update -p quote --precise 1.0.28
21+
cargo update -p proc-macro2 --precise 1.0.63
2022
cargo update -p serde_json --precise 1.0.99
21-
cargo update -p serde --precise 1.0.156
22-
fi
23-
24-
# Pin dependencies required to build with Rust 1.47.0
25-
if cargo --version | grep "1\.47\.0"; then
26-
cargo update -p once_cell --precise 1.13.1
27-
cargo update -p serde_json --precise 1.0.99
28-
cargo update -p serde --precise 1.0.156
23+
cargo update -p serde --precise 1.0.152
24+
cargo update -p log --precise 0.4.18
2925
fi
3026

3127
# Test bitcoind integration tests if told to (this only works with the stable toolchain)

fuzz/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ cargo-fuzz = true
1212
honggfuzz = { version = "0.5.55", default-features = false }
1313
miniscript = { path = "..", features = [ "compiler" ] }
1414

15-
regex = "1.4"
15+
regex = "1.0"
1616

1717
[[bin]]
1818
name = "roundtrip_miniscript_str"

fuzz/fuzz_targets/roundtrip_descriptor.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,27 @@ fn main() {
2323

2424
#[cfg(test)]
2525
mod tests {
26-
use super::*;
26+
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
27+
let mut b = 0;
28+
for (idx, c) in hex.as_bytes().iter().enumerate() {
29+
b <<= 4;
30+
match *c {
31+
b'A'..=b'F' => b |= c - b'A' + 10,
32+
b'a'..=b'f' => b |= c - b'a' + 10,
33+
b'0'..=b'9' => b |= c - b'0',
34+
_ => panic!("Bad hex"),
35+
}
36+
if (idx & 1) == 1 {
37+
out.push(b);
38+
b = 0;
39+
}
40+
}
41+
}
42+
2743
#[test]
28-
fn test() {
29-
do_test(b"pkh()");
44+
fn duplicate_crash3() {
45+
let mut a = Vec::new();
46+
extend_vec_from_hex("747228726970656d616e645f6e5b5c79647228726970656d616e645f6e5b5c7964646464646464646464646464646464646464646464646464646b5f6872702c29", &mut a);
47+
super::do_test(&a);
3048
}
3149
}

src/descriptor/bare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ where
192192
where
193193
T: Translator<P, Q, E>,
194194
{
195-
Ok(Bare::new(self.ms.translate_pk(t)?).map_err(TranslateErr::OuterError)?)
195+
Bare::new(self.ms.translate_pk(t)?).map_err(TranslateErr::OuterError)
196196
}
197197
}
198198

src/descriptor/checksum.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
use core::fmt;
99
use core::iter::FromIterator;
1010

11+
pub use crate::expression::VALID_CHARS;
1112
use crate::prelude::*;
1213
use crate::Error;
1314

14-
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
1515
const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
1616

1717
fn poly_mod(mut c: u64, val: u64) -> u64 {
@@ -101,9 +101,14 @@ impl Engine {
101101
/// state! It is safe to continue feeding it data but the result will not be meaningful.
102102
pub fn input(&mut self, s: &str) -> Result<(), Error> {
103103
for ch in s.chars() {
104-
let pos = INPUT_CHARSET.find(ch).ok_or_else(|| {
105-
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
106-
})? as u64;
104+
let pos = VALID_CHARS
105+
.get(ch as usize)
106+
.ok_or_else(|| {
107+
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
108+
})?
109+
.ok_or_else(|| {
110+
Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch))
111+
})? as u64;
107112
self.c = poly_mod(self.c, pos & 31);
108113
self.cls = self.cls * 3 + (pos >> 5);
109114
self.clscount += 1;

src/descriptor/key.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -999,13 +999,13 @@ impl MiniscriptKey for DescriptorPublicKey {
999999
}
10001000

10011001
fn is_x_only_key(&self) -> bool {
1002-
match self {
1002+
matches!(
1003+
self,
10031004
DescriptorPublicKey::Single(SinglePub {
10041005
key: SinglePubKey::XOnly(ref _key),
10051006
..
1006-
}) => true,
1007-
_ => false,
1008-
}
1007+
})
1008+
)
10091009
}
10101010

10111011
fn num_der_paths(&self) -> usize {

src/descriptor/sh.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,13 @@ impl<Pk: MiniscriptKey + ToPublicKey> Sh<Pk> {
348348
let witness_script = wsh.inner_script().to_v0_p2wsh();
349349
let push_bytes = <&PushBytes>::try_from(witness_script.as_bytes())
350350
.expect("Witness script is not too large");
351-
script::Builder::new().push_slice(&push_bytes).into_script()
351+
script::Builder::new().push_slice(push_bytes).into_script()
352352
}
353353
ShInner::Wpkh(ref wpkh) => {
354354
let redeem_script = wpkh.script_pubkey();
355355
let push_bytes: &PushBytes =
356356
<&PushBytes>::try_from(redeem_script.as_bytes()).expect("Script not too large");
357-
script::Builder::new().push_slice(&push_bytes).into_script()
357+
script::Builder::new().push_slice(push_bytes).into_script()
358358
}
359359
ShInner::SortedMulti(..) | ShInner::Ms(..) => ScriptBuf::new(),
360360
}

src/descriptor/tr.rs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Tr<Pk> {
350350
let builder = bitcoin::blockdata::script::Builder::new();
351351
builder
352352
.push_opcode(opcodes::all::OP_PUSHNUM_1)
353-
.push_slice(&output_key.serialize())
353+
.push_slice(output_key.serialize())
354354
.into_script()
355355
}
356356

@@ -405,8 +405,7 @@ where
405405
type Item = (u8, &'a Miniscript<Pk, Tap>);
406406

407407
fn next(&mut self) -> Option<Self::Item> {
408-
while !self.stack.is_empty() {
409-
let (depth, last) = self.stack.pop().expect("Size checked above");
408+
while let Some((depth, last)) = self.stack.pop() {
410409
match *last {
411410
TapTree::Tree(ref l, ref r) => {
412411
self.stack.push((depth + 1, r));
@@ -519,17 +518,19 @@ impl<Pk: MiniscriptKey> fmt::Display for Tr<Pk> {
519518

520519
// Helper function to parse string into miniscript tree form
521520
fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
522-
for ch in s.bytes() {
523-
if !ch.is_ascii() {
524-
return Err(Error::Unprintable(ch));
525-
}
526-
}
521+
expression::check_valid_chars(s)?;
527522

528523
if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' {
529524
let rest = &s[3..s.len() - 1];
530525
if !rest.contains(',') {
526+
let key = expression::Tree::from_str(rest)?;
527+
if !key.args.is_empty() {
528+
return Err(Error::Unexpected(
529+
"invalid taproot internal key".to_string(),
530+
));
531+
}
531532
let internal_key = expression::Tree {
532-
name: rest,
533+
name: key.name,
533534
args: vec![],
534535
};
535536
return Ok(expression::Tree {
@@ -541,8 +542,14 @@ fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
541542
let (key, script) = split_once(rest, ',')
542543
.ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?;
543544

545+
let key = expression::Tree::from_str(key)?;
546+
if !key.args.is_empty() {
547+
return Err(Error::Unexpected(
548+
"invalid taproot internal key".to_string(),
549+
));
550+
}
544551
let internal_key = expression::Tree {
545-
name: key,
552+
name: key.name,
546553
args: vec![],
547554
};
548555
if script.is_empty() {
@@ -569,19 +576,13 @@ fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> {
569576
if inp.is_empty() {
570577
None
571578
} else {
572-
let mut found = inp.len();
573-
for (idx, ch) in inp.chars().enumerate() {
574-
if ch == delim {
575-
found = idx;
576-
break;
577-
}
578-
}
579-
// No comma or trailing comma found
580-
if found >= inp.len() - 1 {
581-
Some((inp, ""))
582-
} else {
583-
Some((&inp[..found], &inp[found + 1..]))
584-
}
579+
// find the first character that matches delim
580+
let res = inp
581+
.chars()
582+
.position(|ch| ch == delim)
583+
.map(|idx| (&inp[..idx], &inp[idx + 1..]))
584+
.unwrap_or((inp, ""));
585+
Some(res)
585586
}
586587
}
587588

src/expression.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@ use core::str::FromStr;
88
use crate::prelude::*;
99
use crate::{errstr, Error, MAX_RECURSION_DEPTH};
1010

11+
/// Allowed characters are descriptor strings.
12+
pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
13+
14+
/// Map of valid characters in descriptor strings.
15+
#[rustfmt::skip]
16+
pub const VALID_CHARS: [Option<u8>; 128] = [
17+
None, None, None, None, None, None, None, None, None, None, None, None, None,
18+
None, None, None, None, None, None, None, None, None, None, None, None, None,
19+
None, None, None, None, None, None, Some(94), Some(59), Some(92), Some(91),
20+
Some(28), Some(29), Some(50), Some(15), Some(10), Some(11), Some(17), Some(51),
21+
Some(14), Some(52), Some(53), Some(16), Some(0), Some(1), Some(2), Some(3),
22+
Some(4), Some(5), Some(6), Some(7), Some(8), Some(9), Some(27), Some(54),
23+
Some(55), Some(56), Some(57), Some(58), Some(26), Some(82), Some(83),
24+
Some(84), Some(85), Some(86), Some(87), Some(88), Some(89), Some(32), Some(33),
25+
Some(34), Some(35), Some(36), Some(37), Some(38), Some(39), Some(40), Some(41),
26+
Some(42), Some(43), Some(44), Some(45), Some(46), Some(47), Some(48), Some(49),
27+
Some(12), Some(93), Some(13), Some(60), Some(61), Some(90), Some(18), Some(19),
28+
Some(20), Some(21), Some(22), Some(23), Some(24), Some(25), Some(64), Some(65),
29+
Some(66), Some(67), Some(68), Some(69), Some(70), Some(71), Some(72), Some(73),
30+
Some(74), Some(75), Some(76), Some(77), Some(78), Some(79), Some(80), Some(81),
31+
Some(30), Some(62), Some(31), Some(63), None,
32+
];
33+
1134
#[derive(Debug)]
1235
/// A token of the form `x(...)` or `x`
1336
pub struct Tree<'a> {
@@ -166,13 +189,7 @@ impl<'a> Tree<'a> {
166189
/// Parses a tree from a string
167190
#[allow(clippy::should_implement_trait)] // Cannot use std::str::FromStr because of lifetimes.
168191
pub fn from_str(s: &'a str) -> Result<Tree<'a>, Error> {
169-
// Filter out non-ASCII because we byte-index strings all over the
170-
// place and Rust gets very upset when you splinch a string.
171-
for ch in s.bytes() {
172-
if !ch.is_ascii() {
173-
return Err(Error::Unprintable(ch));
174-
}
175-
}
192+
check_valid_chars(s)?;
176193

177194
let (top, rem) = Tree::from_slice(s)?;
178195
if rem.is_empty() {
@@ -183,6 +200,23 @@ impl<'a> Tree<'a> {
183200
}
184201
}
185202

203+
/// Filter out non-ASCII because we byte-index strings all over the
204+
/// place and Rust gets very upset when you splinch a string.
205+
pub fn check_valid_chars(s: &str) -> Result<(), Error> {
206+
for ch in s.bytes() {
207+
if !ch.is_ascii() {
208+
return Err(Error::Unprintable(ch));
209+
}
210+
// Index bounds: We know that ch is ASCII, so it is <= 127.
211+
if VALID_CHARS[ch as usize].is_none() {
212+
return Err(Error::Unexpected(
213+
"Only characters in INPUT_CHARSET are allowed".to_string(),
214+
));
215+
}
216+
}
217+
Ok(())
218+
}
219+
186220
/// Parse a string as a u32, for timelocks or thresholds
187221
pub fn parse_num(s: &str) -> Result<u32, Error> {
188222
if s.len() > 1 {
@@ -253,4 +287,13 @@ mod tests {
253287
assert!(parse_num("+6").is_err());
254288
assert!(parse_num("-6").is_err());
255289
}
290+
291+
#[test]
292+
fn test_valid_char_map() {
293+
let mut valid_chars = [None; 128];
294+
for (i, ch) in super::INPUT_CHARSET.chars().enumerate() {
295+
valid_chars[ch as usize] = Some(i as u8);
296+
}
297+
assert_eq!(valid_chars, super::VALID_CHARS);
298+
}
256299
}

src/interpreter/inner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ fn script_from_stack_elem<Ctx: ScriptContext>(
4343
) -> Result<Miniscript<Ctx::Key, Ctx>, Error> {
4444
match *elem {
4545
stack::Element::Push(sl) => {
46-
Miniscript::parse_with_ext(&bitcoin::Script::from_bytes(sl), &ExtParams::allow_all())
46+
Miniscript::parse_with_ext(bitcoin::Script::from_bytes(sl), &ExtParams::allow_all())
4747
.map_err(Error::from)
4848
}
4949
stack::Element::Satisfied => {

src/iter/tree.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ impl<T: TreeLike> Iterator for PreOrderIter<T> {
220220
self.stack.push(left);
221221
}
222222
Tree::Nary(children) => {
223-
self.stack.extend(children.into_iter().rev().cloned());
223+
self.stack.extend(children.iter().rev().cloned());
224224
}
225225
}
226226
Some(top)

src/miniscript/analyzable.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
221221

222222
/// Whether the given miniscript contains a raw pkh fragment
223223
pub fn contains_raw_pkh(&self) -> bool {
224-
self.iter().any(|ms| match ms.node {
225-
Terminal::RawPkH(_) => true,
226-
_ => false,
227-
})
224+
self.iter().any(|ms| matches!(ms.node, Terminal::RawPkH(_)))
228225
}
229226

230227
/// Check whether the underlying Miniscript is safe under the current context

src/miniscript/astelem.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
465465
Terminal::RawPkH(ref hash) => builder
466466
.push_opcode(opcodes::all::OP_DUP)
467467
.push_opcode(opcodes::all::OP_HASH160)
468-
.push_slice(&hash.to_byte_array())
468+
.push_slice(hash.to_byte_array())
469469
.push_opcode(opcodes::all::OP_EQUALVERIFY),
470470
Terminal::After(t) => builder
471471
.push_int(absolute::LockTime::from(t).to_consensus_u32() as i64)

0 commit comments

Comments
 (0)