diff --git a/Cargo.lock b/Cargo.lock index 4b1503c..d55096e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -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 27e870d..b5a9cee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,9 @@ 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/stringhandler/rust-simplicity.git", branch="feat/add-annex", 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 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(