diff --git a/Cargo.lock b/Cargo.lock index 4b1503c..815d812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -194,9 +194,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -594,9 +594,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miniscript" @@ -873,9 +873,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -885,9 +885,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -896,9 +896,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1033,8 +1039,7 @@ dependencies = [ [[package]] name = "simplicity-lang" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e57bd4d84853974a212eab24ed89da54f49fbccf5e33e93bcd29f0a6591cd5" +source = "git+https://github.com/BlockstreamResearch/rust-simplicity?branch=master#b73d6a52e032156cbdb49ee0736a40e6e01911ce" dependencies = [ "bitcoin", "bitcoin_hashes 0.14.0", @@ -1051,9 +1056,8 @@ dependencies = [ [[package]] name = "simplicity-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875630d128f19818161cefe0a3d910b6aae921d8246711db574a689cb2c11747" +version = "0.6.1" +source = "git+https://github.com/BlockstreamResearch/rust-simplicity?branch=master#b73d6a52e032156cbdb49ee0736a40e6e01911ce" dependencies = [ "bitcoin_hashes 0.14.0", "cc", @@ -1232,34 +1236,22 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if 1.0.0", "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1267,22 +1259,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.111", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 27e870d..e178a9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,8 @@ 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.7.0", features = [ "base64", "serde" ] } +simplicity = { package = "simplicity-lang", git="https://github.com/BlockstreamResearch/rust-simplicity", branch="master", features = [ "base64", "serde" ] } thiserror = "2.0.17" # Daemon-only dependencies diff --git a/src/actions/simplicity/graph.rs b/src/actions/simplicity/graph.rs new file mode 100644 index 0000000..174c311 --- /dev/null +++ b/src/actions/simplicity/graph.rs @@ -0,0 +1,73 @@ +use std::str::FromStr; + +use simplicity::jet; + +use crate::hal_simplicity::Program; + +#[derive(Debug, thiserror::Error)] +pub enum SimplicityGraphError { + #[error("invalid program: {0}")] + ProgramParse(simplicity::ParseError), +} + +#[derive(Debug, Clone, Copy)] +pub enum SharingLevel { + NoSharing, + MaxSharing, +} + +impl FromStr for SharingLevel { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "none" => Ok(SharingLevel::NoSharing), + "max" => Ok(SharingLevel::MaxSharing), + other => Err(format!("unknown sharing level: {}", other)), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum GraphFormat { + Dot, + Mermaid, +} + +impl FromStr for GraphFormat { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "graphviz" | "dot" => Ok(GraphFormat::Dot), + "mermaid" | "mermaidjs" => Ok(GraphFormat::Mermaid), + other => Err(format!("unknown graph format: {}", other)), + } + } +} + +pub fn simplicity_graph( + program: &str, + witness: Option<&str>, + sharing_level: SharingLevel, + graph_format: GraphFormat, +) -> Result { + let program = Program::::from_str(program, witness) + .map_err(SimplicityGraphError::ProgramParse)?; + + let sharing_level = match sharing_level { + SharingLevel::MaxSharing => simplicity::node::SharingLevel::Max, + SharingLevel::NoSharing => simplicity::node::SharingLevel::None, + }; + let graph_format = match graph_format { + GraphFormat::Dot => simplicity::node::GraphFormat::Dot, + GraphFormat::Mermaid => simplicity::node::GraphFormat::Mermaid, + }; + + let res = if let Some(node) = program.redeem_node() { + let graph = node.display_as_graph(graph_format, sharing_level); + graph.to_string() + } else { + let graph = program.commit_prog().display_as_graph(graph_format, sharing_level); + graph.to_string() + }; + Ok(res) +} diff --git a/src/actions/simplicity/mod.rs b/src/actions/simplicity/mod.rs index d2761a3..312cbc4 100644 --- a/src/actions/simplicity/mod.rs +++ b/src/actions/simplicity/mod.rs @@ -1,7 +1,9 @@ +pub mod graph; pub mod info; pub mod pset; pub mod sighash; +pub use graph::*; pub use info::*; pub use sighash::*; diff --git a/src/bin/hal-simplicity/cmd/simplicity/graph.rs b/src/bin/hal-simplicity/cmd/simplicity/graph.rs new file mode 100644 index 0000000..e233f70 --- /dev/null +++ b/src/bin/hal-simplicity/cmd/simplicity/graph.rs @@ -0,0 +1,50 @@ +use clap::value_t; +use hal_simplicity::actions::simplicity::{GraphFormat, SharingLevel}; + +use crate::cmd; + +use super::Error; + +pub fn cmd<'a>() -> clap::App<'a, 'a> { + cmd::subcommand("graph", "Parse a base64-encoded Simplicity program and display a graph").args( + &[ + cmd::arg("program", "a Simplicity program in base64").takes_value(true).required(true), + cmd::arg("witness", "a hex encoding of all the witness data for the program") + .takes_value(true) + .required(false), + cmd::arg( + "sharing", + "the level of node sharing to use when displaying. Either none or max", + ) + .short("s") + .long("sharing") + .takes_value(true) + .required(false) + .default_value("none") + .possible_values(&["none", "max"]), + cmd::arg("format", "the format for the graph, either graphviz (alias dot) or mermaid") + .long("format") + .takes_value(true) + .required(false) + .default_value("graphviz") + .possible_values(&["graphviz", "dot", "mermaid", "mermaidjs"]), + ], + ) +} + +pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { + let program = matches.value_of("program").expect("program is mandatory"); + let witness = matches.value_of("witness"); + let sharing = value_t!(matches, "sharing", SharingLevel).unwrap_or(SharingLevel::NoSharing); + let format = value_t!(matches, "format", GraphFormat).unwrap_or(GraphFormat::Dot); + + match hal_simplicity::actions::simplicity::simplicity_graph(program, witness, sharing, format) { + Ok(graph) => println!("{}", graph), + Err(e) => cmd::print_output( + matches, + &Error { + error: format!("{}", e), + }, + ), + } +} diff --git a/src/bin/hal-simplicity/cmd/simplicity/mod.rs b/src/bin/hal-simplicity/cmd/simplicity/mod.rs index 784798d..2207a4d 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/mod.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/mod.rs @@ -1,6 +1,7 @@ // Copyright 2025 Andrew Poelstra // SPDX-License-Identifier: CC0-1.0 +mod graph; mod info; mod pset; mod sighash; @@ -16,6 +17,7 @@ struct Error { pub fn subcommand<'a>() -> clap::App<'a, 'a> { cmd::subcommand_group("simplicity", "manipulate Simplicity programs") + .subcommand(self::graph::cmd()) .subcommand(self::info::cmd()) .subcommand(self::pset::cmd()) .subcommand(self::sighash::cmd()) @@ -24,6 +26,7 @@ pub fn subcommand<'a>() -> clap::App<'a, 'a> { pub fn execute<'a>(matches: &clap::ArgMatches<'a>) { match matches.subcommand() { ("info", Some(m)) => self::info::exec(m), + ("graph", Some(m)) => self::graph::exec(m), ("pset", Some(m)) => self::pset::exec(m), ("sighash", Some(m)) => self::sighash::exec(m), (_, _) => unreachable!("clap prints help"),