Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Unreleased

- Reject a legacy-pegin miniscript outside `or_d(multi,and_v(v:older,multi))` with `Error::BadDescriptor`.
- Remove the unused `Error::MultiAt`, `Error::MissingHash`, `Error::TaprootSpendInfoUnavialable` and `Error::TrNoExplicitScript` variants.
- Parse expressions with the rust-miniscript `expression` module instead of a local parser. `expression::Tree`, `expression::TreeIterItem` and `expression::Parens` are the rust-miniscript types. `Tree` no longer has public `name` and `args` fields, a `Display` implementation or a `from_slice` method, and `Tree::from_str` returns a rust-miniscript `Error`.
- `FromTree::from_tree`, `expression::{terminal, unary, binary}`, `SortedMultiVec::from_tree` and `Extension::from_name_tree` take `TreeIterItem` nodes instead of `&Tree` references.
- Malformed expressions are reported as rust-miniscript parse errors in `Error::BtcError`. `Error::ExpectedChar` is removed. The expression parser accepts up to 402 nested brackets across the whole expression, including the `eltr` wrapper, script tree branches and the Miniscript in each leaf, and rejects deeper expressions with a rust-miniscript `MaxRecursionDepthExceeded` parse error. The previous limit was 401, and each Taproot leaf had its own separate limit. `Error::MaxRecursiveDepthExceeded` now only reports a Taproot script tree deeper than the 128-level Taproot limit.
- In Elements expressions, curly braces are only accepted for the branches of an `eltr` script tree, which must be nameless and have exactly two children. Keys can no longer contain curly braces. The Bitcoin federation descriptor in `pegin()` follows rust-miniscript rules, which accept curly braces in `tr` script trees.
- Only `,`, `)` or `}` may follow a closing bracket, so key text such as `musig(A,B)/0`, which key types like `String` previously accepted, is rejected.
- A `sortedmulti` now contains a check that threshold must be a number without arguments.
- An empty script tree argument, as in `eltr(A,)`, is rejected. It was previously read as part of the internal key.
- `eltr` accepts a `musig(...)` internal key for key types that parse `musig(...)` text, such as `String`.
- `ct()` descriptors accept `eltr` with script leaves that contain no extension fragments.
- Miniscript and policy strings accept a trailing descriptor checksum, which is verified. The text after the last `#` is read as the checksum, so a key containing `#` is only accepted when a valid checksum follows.
- `pegin()` and `legacy_pegin()` descriptors can be parsed from strings. `LegacyPegin` displays its inner descriptor without the inner checksum. A successful parse does not check the restrictions that pegin spending places on the descriptor. Unsupported forms, such as an `eltr` Elements descriptor, still parse and can panic in the unchanged Bitcoin-side methods, including `bitcoin_address`, `bitcoin_witness_script` and `get_bitcoin_satisfaction`.

# 0.5.0 - Sep 20, 2026

- Update rust-elements to 0.27.0 and simplicity-lang to 0.9.0.
Expand Down
45 changes: 3 additions & 42 deletions Cargo-latest.lock
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,6 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"

[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"

[[package]]
name = "cc"
version = "1.2.34"
Expand Down Expand Up @@ -307,7 +301,6 @@ dependencies = [
"serde",
"serde_json",
"serde_test",
"simplicity-lang",
]

[[package]]
Expand Down Expand Up @@ -342,12 +335,6 @@ dependencies = [
"wasm-bindgen",
]

[[package]]
name = "ghost-cell"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7"

[[package]]
name = "hex-conservative"
version = "0.1.2"
Expand Down Expand Up @@ -467,12 +454,13 @@ dependencies = [

[[package]]
name = "miniscript"
version = "12.3.5"
version = "13.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487906208f38448e186e3deb02f2b8ef046a9078b0de00bdb28bf4fb9b76951c"
checksum = "cd35e2c377504e50159561884b03610711db6c2fec6c89f2b98d94016684726b"
dependencies = [
"bech32 0.11.0",
"bitcoin 0.32.102",
"hex-conservative 1.2.0",
]

[[package]]
Expand Down Expand Up @@ -719,33 +707,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"

[[package]]
name = "simplicity-lang"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff7b10251528d46d9cf60238267911984fedbb0948cccea98dd74fc6bf9f729"
dependencies = [
"bitcoin 0.32.102",
"bitcoin_hashes 1.2.0",
"byteorder",
"elements",
"getrandom",
"ghost-cell",
"hex-conservative 1.2.0",
"miniscript",
"simplicity-sys",
]

[[package]]
name = "simplicity-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955935733b2735665b4735d9b1361eade6be4c7aea6bca2640345e4c81756f58"
dependencies = [
"bitcoin_hashes 1.2.0",
"cc",
]

[[package]]
name = "syn"
version = "2.0.106"
Expand Down
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ base64 = ["bitcoin/base64", "elements/base64"]
[dependencies]
bitcoin = "0.32.0"
elements = "0.27.0"
bitcoin-miniscript = { package = "miniscript", version = "12.0" }
simplicity = { package = "simplicity-lang", version = "0.9.0", optional = true }
bitcoin-miniscript = { package = "miniscript", version = "13.1" }

# Do NOT use this as a feature! Use the `serde` feature instead.
actual-serde = { package = "serde", version = "1.0", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion contrib/test_vars.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
FEATURES_WITH_STD=""

# Test all these features without "std" enabled.
FEATURES_WITHOUT_STD="compiler trace serde rand base64 simplicity"
FEATURES_WITHOUT_STD="compiler trace serde rand base64"

# Run these examples.
EXAMPLES="htlc:compiler parse: sign_multisig: verify_tx: xpub_descriptors: taproot:compiler psbt_sign_finalize:base64"
2 changes: 1 addition & 1 deletion src/confidential/elip151.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ mod test {
(&format!("elwsh(multi(2,{xpub}/<0;1>/*,{xpub}/0/<0;1;2>/*))"), Error::MultipathDescLenMismatch),
] {
let err = confidential_descriptor(invalid_desc).unwrap_err();
assert_eq!(err, expected_err);
assert_eq!(format!("{:?}", err), format!("{:?}", expected_err));
/*
_i = _i + 1;
println!("* Invalid Test vector {}", _i);
Expand Down
40 changes: 22 additions & 18 deletions src/confidential/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,34 +222,38 @@ impl_from_str!(
type Err = Error;,
fn from_str(s: &str) -> Result<Descriptor<Pk, T>, Error> {
let desc_str = verify_checksum(s)?;
let top = expression::Tree::from_str(desc_str)?;
let tree = expression::Tree::from_str(desc_str)?;
let top = tree.root();

if top.name != "ct" {
if top.name() != "ct" {
return Err(Error::BadDescriptor(String::from(
"Not a CT Descriptor",
)));
}
if top.args.len() != 2 {
expression::verify_round_parens(top)?;
let mut args = top.children();
let (Some(keyexpr), Some(desc), None) = (args.next(), args.next(), args.next()) else {
return Err(Error::BadDescriptor(
format!("CT descriptor had {} arguments rather than 2", top.args.len())
format!("CT descriptor had {} arguments rather than 2", top.n_children())
));
}
};

let keyexpr = &top.args[0];
expression::verify_round_parens(keyexpr)?;
let mut keyargs = keyexpr.children();
Ok(Descriptor {
key: match (keyexpr.name, keyexpr.args.len()) {
("elip151", 0) => {
let d = crate::Descriptor::<DescriptorPublicKey>::from_tree(&top.args[1])?;
key: match (keyexpr.name(), keyargs.next(), keyargs.next()) {
("elip151", None, None) => {
let d = crate::Descriptor::<DescriptorPublicKey>::from_tree(desc)?;
Key::from_elip151(&d)?
}
("slip77", 1) => Key::Slip77(expression::terminal(&keyexpr.args[0], slip77::MasterBlindingKey::from_str)?),
("slip77", _) => return Err(Error::BadDescriptor(
("slip77", Some(key), None) => Key::Slip77(expression::terminal(key, slip77::MasterBlindingKey::from_str)?),
("slip77", _, _) => return Err(Error::BadDescriptor(
"slip77() must have exactly one argument".to_owned()
)),
_ => expression::terminal(keyexpr, |s: &str| DescriptorSecretKey::from_str_inner(s, true)).map(Key::View)
.or_else(|_| expression::terminal(keyexpr, DescriptorPublicKey::from_str).map(Key::Bare))?,
},
descriptor: crate::Descriptor::from_tree(&top.args[1])?,
descriptor: crate::Descriptor::from_tree(desc)?,
})
}
);
Expand Down Expand Up @@ -586,10 +590,10 @@ mod tests {
.unwrap(),
};

assert_eq!(
assert!(matches!(
desc.address(&secp, params).unwrap_err(),
Error::Conversion(ConversionError::HardenedChild)
);
));
}

// Same case, reached through `at_derivation_index` on a hardened wildcard blinding
Expand All @@ -605,10 +609,10 @@ mod tests {
let desc = Descriptor::<DescriptorPublicKey, NoExt>::from_str(&desc_str).unwrap();
let definite_desc = desc.at_derivation_index(1).unwrap();

assert_eq!(
assert!(matches!(
definite_desc.address(&secp, params).unwrap_err(),
Error::Conversion(ConversionError::HardenedChild)
);
));
}

#[test]
Expand Down Expand Up @@ -638,10 +642,10 @@ mod tests {
// but we can't enforce this with the Descriptor generic.
let desc_view_str = format!("ct({}/*,elwpkh({}))#ls6mx2ac", xprv, xpub);
let desc_view = Descriptor::<DefiniteDescriptorKey>::from_str(&desc_view_str).unwrap();
assert_eq!(desc_view.address(&secp, params).unwrap_err(), Error::Unexpected("wildcard blinding key".into()));
assert!(matches!(desc_view.address(&secp, params).unwrap_err(), Error::Unexpected(e) if e == "wildcard blinding key"));

let desc_bare_str = format!("ct({}/*,elwpkh({}))#czkz0hwn", xpub, xpub);
let desc_bare = Descriptor::<DefiniteDescriptorKey>::from_str(&desc_bare_str).unwrap();
assert_eq!(desc_bare.address(&secp, params).unwrap_err(), Error::Unexpected("wildcard blinding key".into()));
assert!(matches!(desc_bare.address(&secp, params).unwrap_err(), Error::Unexpected(e) if e == "wildcard blinding key"));
}
}
35 changes: 17 additions & 18 deletions src/descriptor/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::policy::{semantic, Liftable};
use crate::util::{varint_len, witness_to_scriptsig};
use crate::{
elementssig_to_rawsig, BareCtx, Error, ForEachKey, Miniscript, MiniscriptKey, Satisfier,
ToPublicKey, TranslatePk, Translator,
Terminal, ToPublicKey, TranslatePk, Translator,
};

/// Create a Bare Descriptor. That is descriptor that is
Expand Down Expand Up @@ -157,14 +157,10 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Bare<Pk> {

impl_from_tree!(
Bare<Pk>,
fn from_tree(top: &expression::Tree<'_>) -> Result<Self, Error> {
// extra allocations to use the existing code as is.
if top.name.starts_with("el") {
let new_tree = expression::Tree {
name: top.name.split_at(2).1,
args: top.args.clone(),
};
let sub = Miniscript::<Pk, BareCtx>::from_tree(&new_tree)?;
fn from_tree(top: expression::TreeIterItem<'_>) -> Result<Self, Error> {
if let Some(name) = top.name().strip_prefix("el") {
let sub =
Miniscript::<Pk, BareCtx>::from_ast(Terminal::from_tree_with_name(name, top)?)?;
BareCtx::top_level_checks(&sub)?;
Bare::new(sub)
} else {
Expand All @@ -179,7 +175,7 @@ impl_from_str!(
fn from_str(s: &str) -> Result<Self, Self::Err> {
let desc_str = verify_checksum(s)?;
let top = expression::Tree::from_str(&desc_str[2..])?;
Self::from_tree(&top)
Self::from_tree(top.root())
}
);

Expand Down Expand Up @@ -345,16 +341,19 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Pkh<Pk> {

impl_from_tree!(
Pkh<Pk>,
fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
if top.name == "elpkh" && top.args.len() == 1 {
Ok(Pkh::new(expression::terminal(&top.args[0], |pk| {
Pk::from_str(pk)
})?))
fn from_tree(top: expression::TreeIterItem<'_>) -> Result<Self, Error> {
expression::verify_round_parens(top)?;
let mut args = top.children();
if let ("elpkh", Some(child), None) = (top.name(), args.next(), args.next()) {
Ok(Pkh::new(expression::terminal(
child,
|pk| Pk::from_str(pk),
)?))
} else {
Err(Error::Unexpected(format!(
"{}({} args) while parsing pkh descriptor",
top.name,
top.args.len(),
top.name(),
top.n_children(),
)))
}
}
Expand All @@ -366,7 +365,7 @@ impl_from_str!(
fn from_str(s: &str) -> Result<Self, Self::Err> {
let desc_str = verify_checksum(s)?;
let top = expression::Tree::from_str(desc_str)?;
Self::from_tree(&top)
Self::from_tree(top.root())
}
);

Expand Down
Loading
Loading