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
10 changes: 4 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/actions/simplicity/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,6 +19,7 @@ pub struct RedeemInfo {
pub witness_hex: String,
pub amr: Amr,
pub ihr: Ihr,
pub bounds: NodeBounds,
}

#[derive(Serialize)]
Expand Down Expand Up @@ -56,6 +58,7 @@ pub fn simplicity_info(
witness_hex,
amr: node.amr(),
ihr: node.ihr(),
bounds: node.bounds(),
}
});

Expand Down
29 changes: 28 additions & 1 deletion src/actions/simplicity/pset/finalize.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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
Expand All @@ -33,6 +40,7 @@ pub fn pset_finalize(
input_idx: &str,
program: &str,
witness: &str,
annex_padding_size: usize,
genesis_hash: Option<&str>,
) -> Result<UpdatedPset, PsetFinalizeError> {
// 1. Parse everything.
Expand All @@ -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"];

Expand Down
20 changes: 11 additions & 9 deletions src/actions/simplicity/sighash.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::hex_or_base64;
use crate::simplicity::bitcoin::secp256k1::{
schnorr, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey,
};
Expand All @@ -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;
Expand Down Expand Up @@ -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)]
Expand All @@ -113,6 +118,7 @@ pub fn simplicity_sighash(
public_key: Option<&str>,
signature: Option<&str>,
input_utxos: Option<&[&str]>,
annex: Option<&str>,
) -> Result<SighashInfo, SimplicitySighashError> {
let secp = Secp256k1::new();

Expand Down Expand Up @@ -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) => (
Expand Down
5 changes: 5 additions & 0 deletions src/bin/hal-simplicity/cmd/simplicity/pset/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
])
}

Expand All @@ -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),
Expand Down
3 changes: 3 additions & 0 deletions src/bin/hal-simplicity/cmd/simplicity/sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
])
}

Expand All @@ -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<Vec<_>> = matches.values_of("input-utxo").map(|vals| vals.collect());
let annex = matches.value_of("annex");

match hal_simplicity::actions::simplicity::simplicity_sighash(
tx_hex,
Expand All @@ -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(
Expand Down
Loading