From cdde1696c9b0cf277a51e942b44e58aaf4f9d5cc Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 2 Jun 2026 15:51:35 +0200 Subject: [PATCH 1/2] chore: update simplicity-lang to version 0.8.0 - upgraded `simplicity-lang` crate to the latest version --- Cargo.lock | 64 ++----------------------- Cargo.toml | 5 +- src/actions/simplicity/info.rs | 2 +- src/actions/simplicity/pset/decode.rs | 20 ++++++++ src/actions/simplicity/pset/finalize.rs | 2 +- src/actions/simplicity/pset/mod.rs | 2 + src/actions/simplicity/pset/run.rs | 6 +-- src/hal_simplicity.rs | 28 ++++++----- 8 files changed, 50 insertions(+), 79 deletions(-) create mode 100644 src/actions/simplicity/pset/decode.rs diff --git a/Cargo.lock b/Cargo.lock index 4b1503c..a5156a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,15 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] +version = 4 [[package]] name = "ansi_term" @@ -592,12 +583,6 @@ dependencies = [ "cfg-if 0.1.7", ] -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - [[package]] name = "miniscript" version = "12.3.2" @@ -871,50 +856,12 @@ dependencies = [ "redox_syscall 0.1.51", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "ryu" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" -[[package]] -name = "santiago" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de36022292bc2086eb8f55bffa460fef3475e4459b478820711f4c421feb87ec" -dependencies = [ - "regex", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -1032,9 +979,9 @@ dependencies = [ [[package]] name = "simplicity-lang" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e57bd4d84853974a212eab24ed89da54f49fbccf5e33e93bcd29f0a6591cd5" +checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" dependencies = [ "bitcoin", "bitcoin_hashes 0.14.0", @@ -1044,16 +991,15 @@ dependencies = [ "ghost-cell", "hex-conservative 0.2.1", "miniscript", - "santiago", "serde", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875630d128f19818161cefe0a3d910b6aae921d8246711db574a689cb2c11747" +checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" dependencies = [ "bitcoin_hashes 0.14.0", "cc", diff --git a/Cargo.toml b/Cargo.toml index 27e870d..4e86bcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ serde_yaml = "0.8.8" hex = "0.3.2" elements = { version = "0.25.2", features = [ "serde", "base64" ] } -simplicity = { package = "simplicity-lang", version = "0.7.0", features = [ "base64", "serde" ] } +simplicity = { package = "simplicity-lang", version = "0.8.0", features = [ "base64", "serde" ] } thiserror = "2.0.17" # Daemon-only dependencies @@ -55,8 +55,9 @@ hyper-util = { version = "0.1", features = ["tokio"], optional = true } http-body-util = { version = "0.1", optional = true } tokio = { version = "1.48.0", features = ["full"], optional = true } + [lints.clippy] # Exclude lints we don't think are valuable. -needless_question_mark = "allow" # https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 +needless_question_mark = "allow" # https://github.com/rust-lang/rust-clippy/pull/2134 manual_range_contains = "allow" # More readable than clippy's format. uninlined_format_args = "allow" # Stylistic and dumb and inconsistent diff --git a/src/actions/simplicity/info.rs b/src/actions/simplicity/info.rs index c1754e3..95e8d77 100644 --- a/src/actions/simplicity/info.rs +++ b/src/actions/simplicity/info.rs @@ -44,7 +44,7 @@ pub fn simplicity_info( // In the future we should attempt to parse as a Bitcoin program if parsing as // Elements fails. May be tricky/annoying in Rust since Program is a // different type from Program. - let program = Program::::from_str(program, witness) + let program = Program::from_str(program, witness) .map_err(SimplicityInfoError::ProgramParse)?; let redeem_info = program.redeem_node().map(|node| { diff --git a/src/actions/simplicity/pset/decode.rs b/src/actions/simplicity/pset/decode.rs new file mode 100644 index 0000000..4a49f97 --- /dev/null +++ b/src/actions/simplicity/pset/decode.rs @@ -0,0 +1,20 @@ +// Copyright 2025 Andrew Poelstra +// SPDX-License-Identifier: CC0-1.0 + +use serde_json::Value; + +#[derive(Debug, thiserror::Error)] +pub enum PsetDecodeError { + #[error("invalid PSET: {0}")] + PsetParse(elements::pset::ParseError), + + #[error("failed to serialize PSET to JSON: {0}")] + JsonSerialize(serde_json::Error), +} + +/// Decode a PSET into a JSON representation +pub fn pset_decode(pset_b64: &str) -> Result { + let pset: elements::pset::PartiallySignedTransaction = + pset_b64.parse().map_err(PsetDecodeError::PsetParse)?; + serde_json::to_value(&pset).map_err(PsetDecodeError::JsonSerialize) +} diff --git a/src/actions/simplicity/pset/finalize.rs b/src/actions/simplicity/pset/finalize.rs index 8b7d984..8bc8e18 100644 --- a/src/actions/simplicity/pset/finalize.rs +++ b/src/actions/simplicity/pset/finalize.rs @@ -41,7 +41,7 @@ pub fn pset_finalize( let input_idx: u32 = input_idx.parse().map_err(PsetFinalizeError::InputIndexParse)?; let input_idx_usize = input_idx as usize; // 32->usize cast ok on almost all systems - let program = Program::::from_str(program, Some(witness)) + let program = Program::from_str(program, Some(witness)) .map_err(PsetFinalizeError::ProgramParse)?; // 2. Extract transaction environment. diff --git a/src/actions/simplicity/pset/mod.rs b/src/actions/simplicity/pset/mod.rs index 62286bc..b95f6a8 100644 --- a/src/actions/simplicity/pset/mod.rs +++ b/src/actions/simplicity/pset/mod.rs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: CC0-1.0 mod create; +mod decode; mod extract; mod finalize; mod run; mod update_input; pub use create::*; +pub use decode::*; pub use extract::*; pub use finalize::*; pub use run::*; diff --git a/src/actions/simplicity/pset/run.rs b/src/actions/simplicity/pset/run.rs index 5105fd3..56f12d2 100644 --- a/src/actions/simplicity/pset/run.rs +++ b/src/actions/simplicity/pset/run.rs @@ -51,10 +51,10 @@ pub struct RunResponse { struct JetTracker(Vec); -impl ExecTracker for JetTracker { +impl ExecTracker for JetTracker { fn visit_node( &mut self, - node: &simplicity::RedeemNode, + node: &simplicity::RedeemNode, mut input: FrameIter, output: NodeOutput, ) { @@ -107,7 +107,7 @@ pub fn pset_run( let input_idx: u32 = input_idx.parse().map_err(PsetRunError::InputIndexParse)?; let input_idx_usize = input_idx as usize; // 32->usize cast ok on almost all systems - let program = Program::::from_str(program, Some(witness)) + let program = Program::from_str(program, Some(witness)) .map_err(PsetRunError::ProgramParse)?; // 2. Extract transaction environment. diff --git a/src/hal_simplicity.rs b/src/hal_simplicity.rs index 13d2e6e..0c3df89 100644 --- a/src/hal_simplicity.rs +++ b/src/hal_simplicity.rs @@ -5,12 +5,12 @@ use std::sync::Arc; use elements::taproot::{TaprootBuilder, TaprootSpendInfo}; use simplicity::bitcoin::secp256k1; -use simplicity::jet::Jet; +use simplicity::jet::Elements; use simplicity::{BitIter, CommitNode, DecodeError, ParseError, RedeemNode}; /// A representation of a hex or base64-encoded Simplicity program, as seen by /// hal-simplicity. -pub struct Program { +pub struct Program { /// A commitment-time program. This should have no hidden branches (though the /// rust-simplicity encoding allows this) and no witness data. /// @@ -19,13 +19,13 @@ pub struct Program { /// because this lets the tool provide information like CMRs or addresses even /// if there is no witness data available or if the program is improperly /// pruned. - commit_prog: Arc>, + commit_prog: Arc, /// A redemption-time program. This should be pruned (though an unpruned or /// improperly-pruned program can still be parsed) and have witness data. - redeem_prog: Option>>, + redeem_prog: Option>, } -impl Program { +impl Program { /// Constructs a program from a hex representation. /// /// The canonical representation of Simplicity programs is base64, but hex is a @@ -37,14 +37,14 @@ impl Program { pub fn from_str(prog_b64: &str, wit_hex: Option<&str>) -> Result { let prog_bytes = crate::hex_or_base64(prog_b64).map_err(ParseError::Base64)?; let iter = BitIter::new(prog_bytes.iter().copied()); - let commit_prog = CommitNode::decode(iter).map_err(ParseError::Decode)?; + let commit_prog = CommitNode::decode::<_, Elements>(iter).map_err(ParseError::Decode)?; let redeem_prog = wit_hex .map(|wit_hex| { let wit_bytes = crate::hex_or_base64(wit_hex).map_err(ParseError::Base64)?; let prog_iter = BitIter::new(prog_bytes.into_iter()); let wit_iter = BitIter::new(wit_bytes.into_iter()); - RedeemNode::decode(prog_iter, wit_iter).map_err(ParseError::Decode) + RedeemNode::decode::<_, _, Elements>(prog_iter, wit_iter).map_err(ParseError::Decode) }) .transpose()?; @@ -59,8 +59,10 @@ impl Program { let prog_iter = BitIter::from(prog_bytes); let wit_iter = wit_bytes.map(BitIter::from); Ok(Self { - commit_prog: CommitNode::decode(prog_iter.clone())?, - redeem_prog: wit_iter.map(|iter| RedeemNode::decode(prog_iter, iter)).transpose()?, + commit_prog: CommitNode::decode::<_, Elements>(prog_iter.clone())?, + redeem_prog: wit_iter + .map(|iter| RedeemNode::decode::<_, _, Elements>(prog_iter, iter)) + .transpose()?, }) } @@ -80,12 +82,12 @@ impl Program { } /// Accessor for the commitment-time program. - pub fn commit_prog(&self) -> &CommitNode { + pub fn commit_prog(&self) -> &CommitNode { &self.commit_prog } /// Accessor for the commitment-time program. - pub fn redeem_node(&self) -> Option<&Arc>> { + pub fn redeem_node(&self) -> Option<&Arc> { self.redeem_prog.as_ref() } } @@ -165,7 +167,7 @@ mod tests { fn fixed_hex_vector_1() { // Taken from rust-simplicity `assert_lr`. This program works with no witness data. let b64 = "zSQIS29W33fvVt9371bfd+9W33fvVt9371bfd+9W33fvVt93hgGA"; - let prog = Program::::from_str(b64, Some("")).unwrap(); + let prog = Program::from_str(b64, Some("")).unwrap(); assert_eq!( prog.cmr(), @@ -189,7 +191,7 @@ mod tests { // // Maybe in the UI we should detect this case and output some sort of warning? let b64 = "zSQIS29W33fvVt9371bfd+9W33fvVt9371bfd+9W33fvVt93hgGA"; - let prog = Program::::from_str(b64, None).unwrap(); + let prog = Program::from_str(b64, None).unwrap(); assert_eq!( prog.cmr(), From 91728b39df22fb790ff16ac2f613b5f58394bf6a Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 2 Jun 2026 15:54:03 +0200 Subject: [PATCH 2/2] feat(pset decode): add command to decode PSET into JSON --- src/actions/simplicity/info.rs | 5 ++-- src/actions/simplicity/pset/finalize.rs | 5 ++-- src/actions/simplicity/pset/run.rs | 5 ++-- .../cmd/simplicity/pset/decode.rs | 24 +++++++++++++++++++ .../hal-simplicity/cmd/simplicity/pset/mod.rs | 3 +++ .../hal-simplicity/cmd/simplicity/pset/run.rs | 1 + src/hal_simplicity.rs | 3 ++- 7 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 src/bin/hal-simplicity/cmd/simplicity/pset/decode.rs diff --git a/src/actions/simplicity/info.rs b/src/actions/simplicity/info.rs index 95e8d77..93a7561 100644 --- a/src/actions/simplicity/info.rs +++ b/src/actions/simplicity/info.rs @@ -1,6 +1,6 @@ use crate::hal_simplicity::{elements_address, Program}; use crate::simplicity::hex::parse::FromHex as _; -use crate::simplicity::{jet, Amr, Cmr, Ihr}; +use crate::simplicity::{Amr, Cmr, Ihr}; use serde::Serialize; #[derive(Debug, thiserror::Error)] @@ -44,8 +44,7 @@ pub fn simplicity_info( // In the future we should attempt to parse as a Bitcoin program if parsing as // Elements fails. May be tricky/annoying in Rust since Program is a // different type from Program. - let program = Program::from_str(program, witness) - .map_err(SimplicityInfoError::ProgramParse)?; + let program = Program::from_str(program, witness).map_err(SimplicityInfoError::ProgramParse)?; let redeem_info = program.redeem_node().map(|node| { let disp = node.display(); diff --git a/src/actions/simplicity/pset/finalize.rs b/src/actions/simplicity/pset/finalize.rs index 8bc8e18..7fed226 100644 --- a/src/actions/simplicity/pset/finalize.rs +++ b/src/actions/simplicity/pset/finalize.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC0-1.0 use crate::hal_simplicity::Program; -use crate::simplicity::jet; use super::{execution_environment, PsetError, UpdatedPset}; @@ -41,8 +40,8 @@ pub fn pset_finalize( let input_idx: u32 = input_idx.parse().map_err(PsetFinalizeError::InputIndexParse)?; let input_idx_usize = input_idx as usize; // 32->usize cast ok on almost all systems - let program = Program::from_str(program, Some(witness)) - .map_err(PsetFinalizeError::ProgramParse)?; + let program = + Program::from_str(program, Some(witness)).map_err(PsetFinalizeError::ProgramParse)?; // 2. Extract transaction environment. let (tx_env, control_block, tap_leaf) = diff --git a/src/actions/simplicity/pset/run.rs b/src/actions/simplicity/pset/run.rs index 56f12d2..7c10882 100644 --- a/src/actions/simplicity/pset/run.rs +++ b/src/actions/simplicity/pset/run.rs @@ -5,8 +5,8 @@ use serde::Serialize; use crate::hal_simplicity::Program; use crate::simplicity::bit_machine::{BitMachine, ExecTracker, FrameIter, NodeOutput}; +use crate::simplicity::node; use crate::simplicity::Value; -use crate::simplicity::{jet, node}; use super::{execution_environment, PsetError}; @@ -107,8 +107,7 @@ pub fn pset_run( let input_idx: u32 = input_idx.parse().map_err(PsetRunError::InputIndexParse)?; let input_idx_usize = input_idx as usize; // 32->usize cast ok on almost all systems - let program = Program::from_str(program, Some(witness)) - .map_err(PsetRunError::ProgramParse)?; + let program = Program::from_str(program, Some(witness)).map_err(PsetRunError::ProgramParse)?; // 2. Extract transaction environment. let (tx_env, _control_block, _tap_leaf) = diff --git a/src/bin/hal-simplicity/cmd/simplicity/pset/decode.rs b/src/bin/hal-simplicity/cmd/simplicity/pset/decode.rs new file mode 100644 index 0000000..9d89abb --- /dev/null +++ b/src/bin/hal-simplicity/cmd/simplicity/pset/decode.rs @@ -0,0 +1,24 @@ +// Copyright 2025 Andrew Poelstra +// SPDX-License-Identifier: CC0-1.0 + +use super::super::Error; +use crate::cmd; + +pub fn cmd<'a>() -> clap::App<'a, 'a> { + cmd::subcommand("decode", "decode a PSET into JSON") + .args(&cmd::opts_networks()) + .args(&[cmd::arg("pset", "PSET to decode (base64)").takes_value(true).required(true)]) +} + +pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { + let pset_b64 = matches.value_of("pset").expect("pset mandatory"); + match hal_simplicity::actions::simplicity::pset::pset_decode(pset_b64) { + Ok(info) => cmd::print_output(matches, &info), + Err(e) => cmd::print_output( + matches, + &Error { + error: format!("{}", e), + }, + ), + } +} diff --git a/src/bin/hal-simplicity/cmd/simplicity/pset/mod.rs b/src/bin/hal-simplicity/cmd/simplicity/pset/mod.rs index f5f12cf..61eaa3c 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/pset/mod.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/pset/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: CC0-1.0 mod create; +mod decode; mod extract; mod finalize; mod run; @@ -12,6 +13,7 @@ use crate::cmd; pub fn cmd<'a>() -> clap::App<'a, 'a> { cmd::subcommand_group("pset", "manipulate PSETs for spending from Simplicity programs") .subcommand(self::create::cmd()) + .subcommand(self::decode::cmd()) .subcommand(self::extract::cmd()) .subcommand(self::finalize::cmd()) .subcommand(self::run::cmd()) @@ -21,6 +23,7 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { match matches.subcommand() { ("create", Some(m)) => self::create::exec(m), + ("decode", Some(m)) => self::decode::exec(m), ("extract", Some(m)) => self::extract::exec(m), ("finalize", Some(m)) => self::finalize::exec(m), ("run", Some(m)) => self::run::exec(m), diff --git a/src/bin/hal-simplicity/cmd/simplicity/pset/run.rs b/src/bin/hal-simplicity/cmd/simplicity/pset/run.rs index 8a27d19..407d8fc 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/pset/run.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/pset/run.rs @@ -21,6 +21,7 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { "genesis hash of the blockchain the transaction belongs to (hex)", ) .short("g") + .takes_value(true) .required(false), ]) } diff --git a/src/hal_simplicity.rs b/src/hal_simplicity.rs index 0c3df89..9783a9f 100644 --- a/src/hal_simplicity.rs +++ b/src/hal_simplicity.rs @@ -44,7 +44,8 @@ impl Program { let wit_bytes = crate::hex_or_base64(wit_hex).map_err(ParseError::Base64)?; let prog_iter = BitIter::new(prog_bytes.into_iter()); let wit_iter = BitIter::new(wit_bytes.into_iter()); - RedeemNode::decode::<_, _, Elements>(prog_iter, wit_iter).map_err(ParseError::Decode) + RedeemNode::decode::<_, _, Elements>(prog_iter, wit_iter) + .map_err(ParseError::Decode) }) .transpose()?;