From 735e7085f58f2da2760693444d9b4ae309cdb028 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Mon, 23 Feb 2026 14:50:27 +0200 Subject: [PATCH 1/3] feat: add ability to add annex padding --- src/actions/simplicity/info.rs | 3 ++ src/actions/simplicity/pset/finalize.rs | 29 ++++++++++++++++++- src/actions/simplicity/sighash.rs | 20 +++++++------ .../cmd/simplicity/pset/finalize.rs | 5 ++++ .../hal-simplicity/cmd/simplicity/sighash.rs | 3 ++ 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/actions/simplicity/info.rs b/src/actions/simplicity/info.rs index c1754e3..27eb58f 100644 --- a/src/actions/simplicity/info.rs +++ b/src/actions/simplicity/info.rs @@ -2,6 +2,7 @@ use crate::hal_simplicity::{elements_address, Program}; use crate::simplicity::hex::parse::FromHex as _; use crate::simplicity::{jet, Amr, Cmr, Ihr}; use serde::Serialize; +use simplicity::NodeBounds; #[derive(Debug, thiserror::Error)] pub enum SimplicityInfoError { @@ -18,6 +19,7 @@ pub struct RedeemInfo { pub witness_hex: String, pub amr: Amr, pub ihr: Ihr, + pub bounds: NodeBounds, } #[derive(Serialize)] @@ -56,6 +58,7 @@ pub fn simplicity_info( witness_hex, amr: node.amr(), ihr: node.ihr(), + bounds: node.bounds(), } }); diff --git a/src/actions/simplicity/pset/finalize.rs b/src/actions/simplicity/pset/finalize.rs index 8b7d984..cdfd370 100644 --- a/src/actions/simplicity/pset/finalize.rs +++ b/src/actions/simplicity/pset/finalize.rs @@ -1,6 +1,8 @@ // Copyright 2025 Andrew Poelstra // SPDX-License-Identifier: CC0-1.0 +use elements::bitcoin::taproot::TAPROOT_ANNEX_PREFIX; + use crate::hal_simplicity::Program; use crate::simplicity::jet; @@ -25,6 +27,11 @@ pub enum PsetFinalizeError { #[error("failed to prune program: {0}")] ProgramPrune(simplicity::bit_machine::ExecutionError), + + #[error("cost of transaction exceeds budget. Suggested annex padding bytes to add (including prefix): {padding_required}")] + CostExceedsBudget { + padding_required: usize, + }, } /// Attach a Simplicity program and witness to a PSET input @@ -33,6 +40,7 @@ pub fn pset_finalize( input_idx: &str, program: &str, witness: &str, + annex_padding_size: usize, genesis_hash: Option<&str>, ) -> Result { // 1. Parse everything. @@ -56,7 +64,26 @@ pub fn pset_finalize( let (prog, witness) = pruned.to_vec_with_witness(); // If `execution_environment` above succeeded we are guaranteed that this index is in bounds. let input = &mut pset.inputs_mut()[input_idx_usize]; - input.final_script_witness = Some(vec![witness, prog, tap_leaf.into_bytes(), cb_serialized]); + + let final_script_witness = if annex_padding_size == 0 { + vec![witness, prog, tap_leaf.into_bytes(), cb_serialized] + } else { + let mut annex = vec![0u8; annex_padding_size]; + annex[0] = TAPROOT_ANNEX_PREFIX; + + vec![witness, prog, tap_leaf.into_bytes(), cb_serialized, annex] + }; + + // Calculate the budget and warn the user if it is not enough. + let cost = pruned.bounds().cost; + if !cost.is_budget_valid(&final_script_witness) { + let padding_required = cost.get_padding(&final_script_witness).map(|bytes| bytes.len()); + return Err(PsetFinalizeError::CostExceedsBudget { + padding_required: padding_required.unwrap_or_default(), + }); + } + + input.final_script_witness = Some(final_script_witness); let updated_values = vec!["final_script_witness"]; diff --git a/src/actions/simplicity/sighash.rs b/src/actions/simplicity/sighash.rs index 6ac9640..841bc3c 100644 --- a/src/actions/simplicity/sighash.rs +++ b/src/actions/simplicity/sighash.rs @@ -1,3 +1,4 @@ +use crate::hex_or_base64; use crate::simplicity::bitcoin::secp256k1::{ schnorr, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey, }; @@ -12,6 +13,7 @@ use elements::bitcoin::secp256k1; use elements::hashes::Hash as _; use elements::pset::PartiallySignedTransaction; use serde::Serialize; +use simplicity::base64; use crate::simplicity::elements::taproot::ControlBlock; use crate::simplicity::jet::elements::ElementsEnv; @@ -92,6 +94,9 @@ pub enum SimplicitySighashError { #[error("invalid input UTXO: {0}")] InputUtxoParsing(ParseElementsUtxoError), + + #[error("annex was not valid hex or base64:{0}")] + AnnexParsing(base64::DecodeError), } #[derive(Serialize)] @@ -113,6 +118,7 @@ pub fn simplicity_sighash( public_key: Option<&str>, signature: Option<&str>, input_utxos: Option<&[&str]>, + annex: Option<&str>, ) -> Result { let secp = Secp256k1::new(); @@ -214,15 +220,11 @@ pub fn simplicity_sighash( ]), }; - let tx_env = ElementsEnv::new( - &tx, - input_utxos, - input_idx, - cmr, - control_block, - None, // FIXME populate this; needs https://github.com/BlockstreamResearch/rust-simplicity/issues/315 first - genesis_hash, - ); + let annex = annex + .map(|v| hex_or_base64(v).map_err(SimplicitySighashError::AnnexParsing)) + .transpose()?; + let tx_env = + ElementsEnv::new(&tx, input_utxos, input_idx, cmr, control_block, annex, genesis_hash); let (pk, sig) = match (public_key, signature) { (Some(pk), None) => ( diff --git a/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs b/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs index 8e3a694..78a23e7 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs @@ -22,6 +22,8 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { ) .short("g") .required(false), + cmd::arg("padding-bytes", "The number of padding bytes to add to the annex to allow the cost of the program to fall in budget. This number should include the prefix byte.").alias("padding") + .short("a").required(false) ]) } @@ -31,12 +33,15 @@ pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { let program = matches.value_of("program").expect("program is mandatory"); let witness = matches.value_of("witness").expect("witness is mandatory"); let genesis_hash = matches.value_of("genesis-hash"); + let padding_bytes = + matches.value_of("padding-bytes").map(|b| b.parse().ok()).flatten().unwrap_or_default(); match hal_simplicity::actions::simplicity::pset::pset_finalize( pset_b64, input_idx, program, witness, + padding_bytes, genesis_hash, ) { Ok(info) => cmd::print_output(matches, &info), diff --git a/src/bin/hal-simplicity/cmd/simplicity/sighash.rs b/src/bin/hal-simplicity/cmd/simplicity/sighash.rs index a685f9e..5478204 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/sighash.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/sighash.rs @@ -38,6 +38,7 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { .multiple(true) .number_of_values(1) .required(false), + cmd::opt("annex", "addition annex, as hex. Mainly used to add padding for CPU intensive programs").required(false) ]) } @@ -51,6 +52,7 @@ pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { let public_key = matches.value_of("public-key"); let signature = matches.value_of("signature"); let input_utxos: Option> = matches.values_of("input-utxo").map(|vals| vals.collect()); + let annex = matches.value_of("annex"); match hal_simplicity::actions::simplicity::simplicity_sighash( tx_hex, @@ -62,6 +64,7 @@ pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { public_key, signature, input_utxos.as_deref(), + annex, ) { Ok(info) => cmd::print_output(matches, &info), Err(e) => cmd::print_output( From 6e3991de489f5debb83ff3d89a9b6f378ee74a1f Mon Sep 17 00:00:00 2001 From: stringhandler Date: Mon, 23 Feb 2026 15:04:36 +0200 Subject: [PATCH 2/3] add link to rust simplicity --- Cargo.lock | 8 +++----- Cargo.toml | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f77b316..6d55616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1033,8 +1033,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/stringhandler/rust-simplicity.git?branch=feat%2Fadd-annex#7ce98ca88b5845de64dd630c890ba899efeaf54d" dependencies = [ "bitcoin", "bitcoin_hashes 0.14.0", @@ -1051,9 +1050,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/stringhandler/rust-simplicity.git?branch=feat%2Fadd-annex#7ce98ca88b5845de64dd630c890ba899efeaf54d" dependencies = [ "bitcoin_hashes 0.14.0", "cc", diff --git a/Cargo.toml b/Cargo.toml index d8154d0..7f88397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,9 @@ serde_yaml = "0.8.8" hex = "0.3.2" elements = { version = "0.25.3", 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/stringhandler/rust-simplicity.git", branch="feat/add-annex", features = ["base64", "serde"]} + thiserror = "2.0.17" # Daemon-only dependencies From 1c259f0264c6bd8d7ffa6f91cdfd8f30fa6e8990 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 20 Oct 2026 12:08:11 +0200 Subject: [PATCH 3/3] wip --- Cargo.lock | 64 ++----------------- Cargo.toml | 4 +- src/actions/simplicity/info.rs | 6 +- src/actions/simplicity/pset/finalize.rs | 47 ++++++++------ src/actions/simplicity/pset/run.rs | 9 ++- src/actions/simplicity/sighash.rs | 32 ++++++++-- .../cmd/simplicity/pset/finalize.rs | 23 +++++-- .../hal-simplicity/cmd/simplicity/sighash.rs | 4 +- src/daemon/handler.rs | 2 + src/daemon/types.rs | 2 + src/hal_simplicity.rs | 29 +++++---- 11 files changed, 107 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d55616..e71a00f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "ansi_term" version = "0.11.0" @@ -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,8 +979,9 @@ dependencies = [ [[package]] name = "simplicity-lang" -version = "0.7.0" -source = "git+https://github.com/stringhandler/rust-simplicity.git?branch=feat%2Fadd-annex#7ce98ca88b5845de64dd630c890ba899efeaf54d" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" dependencies = [ "bitcoin", "bitcoin_hashes 0.14.0", @@ -1043,15 +991,15 @@ dependencies = [ "ghost-cell", "hex-conservative 0.2.1", "miniscript", - "santiago", "serde", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.6.1" -source = "git+https://github.com/stringhandler/rust-simplicity.git?branch=feat%2Fadd-annex#7ce98ca88b5845de64dd630c890ba899efeaf54d" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" dependencies = [ "bitcoin_hashes 0.14.0", "cc", diff --git a/Cargo.toml b/Cargo.toml index 7f88397..8c0af83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,7 @@ serde_yaml = "0.8.8" hex = "0.3.2" elements = { version = "0.25.3", features = [ "serde", "base64" ] } -# simplicity = { package = "simplicity-lang", version = "0.7.0", features = [ "base64", "serde" ] } -simplicity = { package="simplicity-lang", git="https://github.com/stringhandler/rust-simplicity.git", branch="feat/add-annex", features = ["base64", "serde"]} - +simplicity = { package = "simplicity-lang", version = "0.8.0", features = [ "base64", "serde" ] } thiserror = "2.0.17" # Daemon-only dependencies diff --git a/src/actions/simplicity/info.rs b/src/actions/simplicity/info.rs index 27eb58f..4303f8d 100644 --- a/src/actions/simplicity/info.rs +++ b/src/actions/simplicity/info.rs @@ -1,8 +1,7 @@ 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, NodeBounds}; use serde::Serialize; -use simplicity::NodeBounds; #[derive(Debug, thiserror::Error)] pub enum SimplicityInfoError { @@ -46,8 +45,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 cdfd370..9c55a66 100644 --- a/src/actions/simplicity/pset/finalize.rs +++ b/src/actions/simplicity/pset/finalize.rs @@ -4,7 +4,6 @@ use elements::bitcoin::taproot::TAPROOT_ANNEX_PREFIX; use crate::hal_simplicity::Program; -use crate::simplicity::jet; use super::{execution_environment, PsetError, UpdatedPset}; @@ -28,9 +27,10 @@ pub enum PsetFinalizeError { #[error("failed to prune program: {0}")] ProgramPrune(simplicity::bit_machine::ExecutionError), - #[error("cost of transaction exceeds budget. Suggested annex padding bytes to add (including prefix): {padding_required}")] + #[error("program cost exceeds the witness budget: needs an annex of at least {required} bytes (including the 0x50 prefix), got {provided}")] CostExceedsBudget { - padding_required: usize, + required: usize, + provided: usize, }, } @@ -49,8 +49,21 @@ 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)?; + + let annex = (annex_padding_size > 0).then(|| { + let mut annex = vec![0u8; annex_padding_size]; + annex[0] = TAPROOT_ANNEX_PREFIX; + annex + }); + + // The sighash commits to the annex, so the program has to run against a transaction + // that has it. rust-simplicity reads the annex from the input's witness, so stage it + // there. This also discards the witness from any earlier finalization. + if let Some(input) = pset.inputs_mut().get_mut(input_idx_usize) { + input.final_script_witness = annex.clone().map(|annex| vec![annex]); + } // 2. Extract transaction environment. let (tx_env, control_block, tap_leaf) = @@ -62,28 +75,22 @@ pub fn pset_finalize( let pruned = redeem_node.prune(&tx_env).map_err(PsetFinalizeError::ProgramPrune)?; let (prog, witness) = pruned.to_vec_with_witness(); - // If `execution_environment` above succeeded we are guaranteed that this index is in bounds. - let input = &mut pset.inputs_mut()[input_idx_usize]; + let mut final_script_witness = vec![witness, prog, tap_leaf.into_bytes(), cb_serialized]; - let final_script_witness = if annex_padding_size == 0 { - vec![witness, prog, tap_leaf.into_bytes(), cb_serialized] - } else { - let mut annex = vec![0u8; annex_padding_size]; - annex[0] = TAPROOT_ANNEX_PREFIX; - - vec![witness, prog, tap_leaf.into_bytes(), cb_serialized, annex] - }; - - // Calculate the budget and warn the user if it is not enough. + // 4. Check that the witness pays for the program's cost. The required padding is + // computed before adding the annex so that it is the total annex size needed. let cost = pruned.bounds().cost; + let required = cost.get_padding(&final_script_witness).map_or(0, |annex| annex.len()); + final_script_witness.extend(annex); if !cost.is_budget_valid(&final_script_witness) { - let padding_required = cost.get_padding(&final_script_witness).map(|bytes| bytes.len()); return Err(PsetFinalizeError::CostExceedsBudget { - padding_required: padding_required.unwrap_or_default(), + required, + provided: annex_padding_size, }); } - input.final_script_witness = Some(final_script_witness); + // If `execution_environment` above succeeded we are guaranteed that this index is in bounds. + pset.inputs_mut()[input_idx_usize].final_script_witness = Some(final_script_witness); let updated_values = vec!["final_script_witness"]; diff --git a/src/actions/simplicity/pset/run.rs b/src/actions/simplicity/pset/run.rs index 5105fd3..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}; @@ -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,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/actions/simplicity/sighash.rs b/src/actions/simplicity/sighash.rs index 841bc3c..0f924f2 100644 --- a/src/actions/simplicity/sighash.rs +++ b/src/actions/simplicity/sighash.rs @@ -1,4 +1,5 @@ use crate::hex_or_base64; +use crate::simplicity::base64; use crate::simplicity::bitcoin::secp256k1::{ schnorr, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey, }; @@ -10,10 +11,10 @@ use crate::simplicity::jet::elements::ElementsUtxo; use crate::simplicity::Cmr; use elements::bitcoin::secp256k1; +use elements::bitcoin::taproot::TAPROOT_ANNEX_PREFIX; use elements::hashes::Hash as _; use elements::pset::PartiallySignedTransaction; use serde::Serialize; -use simplicity::base64; use crate::simplicity::elements::taproot::ControlBlock; use crate::simplicity::jet::elements::ElementsEnv; @@ -95,8 +96,11 @@ pub enum SimplicitySighashError { #[error("invalid input UTXO: {0}")] InputUtxoParsing(ParseElementsUtxoError), - #[error("annex was not valid hex or base64:{0}")] + #[error("invalid annex: {0}")] AnnexParsing(base64::DecodeError), + + #[error("annex must start with the 0x50 prefix byte")] + AnnexMissingPrefix, } #[derive(Serialize)] @@ -130,7 +134,7 @@ pub fn simplicity_sighash( // 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 tx = match pset { + let mut tx = match pset { Some(ref pset) => pset.extract_tx().map_err(SimplicitySighashError::PsetExtraction)?, None => { let tx_bytes = @@ -220,11 +224,25 @@ pub fn simplicity_sighash( ]), }; - let annex = annex - .map(|v| hex_or_base64(v).map_err(SimplicitySighashError::AnnexParsing)) - .transpose()?; + // The sighash commits to the annex. rust-simplicity reads it from the last element of the + // input's witness, not from the `ElementsEnv` annex parameter, so put it there. + if let Some(annex) = annex { + let annex = hex_or_base64(annex).map_err(SimplicitySighashError::AnnexParsing)?; + if annex.first() != Some(&TAPROOT_ANNEX_PREFIX) { + return Err(SimplicitySighashError::AnnexMissingPrefix); + } + let n_inputs = tx.input.len(); + let input = tx.input.get_mut(input_idx as usize).ok_or( + SimplicitySighashError::InputIndexOutOfRange { + index: input_idx, + n_inputs, + }, + )?; + input.witness.script_witness = vec![annex]; + } + let tx_env = - ElementsEnv::new(&tx, input_utxos, input_idx, cmr, control_block, annex, genesis_hash); + ElementsEnv::new(&tx, input_utxos, input_idx, cmr, control_block, None, genesis_hash); let (pk, sig) = match (public_key, signature) { (Some(pk), None) => ( diff --git a/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs b/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs index 78a23e7..3970ddd 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs @@ -22,8 +22,14 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { ) .short("g") .required(false), - cmd::arg("padding-bytes", "The number of padding bytes to add to the annex to allow the cost of the program to fall in budget. This number should include the prefix byte.").alias("padding") - .short("a").required(false) + cmd::opt( + "padding-bytes", + "size of the annex to pad the witness with so the program's cost fits its budget, including the 0x50 prefix byte (decimal)", + ) + .short("a") + .alias("padding") + .takes_value(true) + .required(false), ]) } @@ -33,8 +39,17 @@ pub fn exec<'a>(matches: &clap::ArgMatches<'a>) { let program = matches.value_of("program").expect("program is mandatory"); let witness = matches.value_of("witness").expect("witness is mandatory"); let genesis_hash = matches.value_of("genesis-hash"); - let padding_bytes = - matches.value_of("padding-bytes").map(|b| b.parse().ok()).flatten().unwrap_or_default(); + let padding_bytes = match matches.value_of("padding-bytes").map(str::parse).transpose() { + Ok(n) => n.unwrap_or(0), + Err(e) => { + return cmd::print_output( + matches, + &Error { + error: format!("invalid padding-bytes: {}", e), + }, + ) + } + }; match hal_simplicity::actions::simplicity::pset::pset_finalize( pset_b64, diff --git a/src/bin/hal-simplicity/cmd/simplicity/sighash.rs b/src/bin/hal-simplicity/cmd/simplicity/sighash.rs index 5478204..930b0ae 100644 --- a/src/bin/hal-simplicity/cmd/simplicity/sighash.rs +++ b/src/bin/hal-simplicity/cmd/simplicity/sighash.rs @@ -38,7 +38,9 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> { .multiple(true) .number_of_values(1) .required(false), - cmd::opt("annex", "addition annex, as hex. Mainly used to add padding for CPU intensive programs").required(false) + cmd::opt("annex", "taproot annex of the input, starting with the 0x50 prefix byte. Used to pad the witness of CPU-intensive programs; must match the annex added by `pset finalize --padding-bytes` (hex or base64)") + .takes_value(true) + .required(false), ]) } diff --git a/src/daemon/handler.rs b/src/daemon/handler.rs index 2298b8d..afc23c3 100644 --- a/src/daemon/handler.rs +++ b/src/daemon/handler.rs @@ -160,6 +160,7 @@ impl RpcHandler for DefaultRpcHandler { req.public_key.as_deref(), req.signature.as_deref(), input_utxos.as_deref(), + req.annex.as_deref(), ) .map_err(|e| RpcError::custom(ErrorCode::InternalError.code(), e.to_string()))?; serialize_result(result) @@ -190,6 +191,7 @@ impl RpcHandler for DefaultRpcHandler { &req.input_index.to_string(), &req.program, &req.witness, + req.padding_bytes.unwrap_or(0), req.genesis_hash.as_deref(), ) .map_err(|e| RpcError::custom(ErrorCode::InternalError.code(), e.to_string()))?; diff --git a/src/daemon/types.rs b/src/daemon/types.rs index cddf6bf..4da90bc 100644 --- a/src/daemon/types.rs +++ b/src/daemon/types.rs @@ -140,6 +140,7 @@ pub struct SimplicitySighashRequest { pub public_key: Option, pub signature: Option, pub input_utxos: Option>, + pub annex: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -180,6 +181,7 @@ pub struct PsetFinalizeRequest { pub program: String, pub witness: String, pub genesis_hash: Option, + pub padding_bytes: Option, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/hal_simplicity.rs b/src/hal_simplicity.rs index 13d2e6e..9783a9f 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,15 @@ 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 +60,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 +83,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 +168,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 +192,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(),