hal_simplicity/actions/simplicity/pset/
mod.rs

1// Copyright 2025 Andrew Poelstra
2// SPDX-License-Identifier: CC0-1.0
3
4mod create;
5mod extract;
6mod finalize;
7mod run;
8mod update_input;
9
10pub use create::*;
11pub use extract::*;
12pub use finalize::*;
13pub use run::*;
14pub use update_input::*;
15
16use std::sync::Arc;
17
18use elements::hashes::Hash as _;
19use elements::pset::PartiallySignedTransaction;
20use elements::taproot::ControlBlock;
21use elements::Script;
22use serde::Serialize;
23
24use crate::simplicity::jet::elements::{ElementsEnv, ElementsUtxo};
25use crate::simplicity::Cmr;
26
27#[derive(Debug, thiserror::Error)]
28pub enum PsetError {
29	#[error("input index {index} out-of-range for PSET with {total} inputs")]
30	InputIndexOutOfRange {
31		index: usize,
32		total: usize,
33	},
34
35	#[error("failed to parse genesis hash: {0}")]
36	GenesisHashParse(elements::hashes::hex::HexToArrayError),
37
38	#[error("could not find Simplicity leaf in PSET taptree with CMR {cmr})")]
39	MissingSimplicityLeaf {
40		cmr: String,
41	},
42
43	#[error("failed to extract transaction from PSET: {0}")]
44	PsetExtract(elements::pset::Error),
45
46	#[error("witness_utxo field not populated for input {0}")]
47	MissingWitnessUtxo(usize),
48}
49
50#[derive(Serialize)]
51pub struct UpdatedPset {
52	pub pset: String,
53	pub updated_values: Vec<&'static str>,
54}
55
56/// Helper function to create execution environment for PSET operations
57pub fn execution_environment(
58	pset: &PartiallySignedTransaction,
59	input_idx: usize,
60	cmr: Cmr,
61	genesis_hash: Option<&str>,
62) -> Result<(ElementsEnv<Arc<elements::Transaction>>, ControlBlock, Script), PsetError> {
63	let n_inputs = pset.n_inputs();
64	let input = pset.inputs().get(input_idx).ok_or(PsetError::InputIndexOutOfRange {
65		index: input_idx,
66		total: n_inputs,
67	})?;
68
69	// Default to Liquid Testnet genesis block
70	let genesis_hash = match genesis_hash {
71		Some(s) => s.parse().map_err(PsetError::GenesisHashParse)?,
72		None => elements::BlockHash::from_byte_array([
73			// copied out of simplicity-webide source
74			0xc1, 0xb1, 0x6a, 0xe2, 0x4f, 0x24, 0x23, 0xae, 0xa2, 0xea, 0x34, 0x55, 0x22, 0x92,
75			0x79, 0x3b, 0x5b, 0x5e, 0x82, 0x99, 0x9a, 0x1e, 0xed, 0x81, 0xd5, 0x6a, 0xee, 0x52,
76			0x8e, 0xda, 0x71, 0xa7,
77		]),
78	};
79
80	// Unlike in the 'update-input' case we don't insist on any particular form of
81	// the Taptree. We just look for the CMR in the list.
82	let mut control_block_leaf = None;
83	for (cb, script_ver) in &input.tap_scripts {
84		if script_ver.1 == simplicity::leaf_version() && &script_ver.0[..] == cmr.as_ref() {
85			control_block_leaf = Some((cb.clone(), script_ver.0.clone()));
86		}
87	}
88	let (control_block, tap_leaf) = match control_block_leaf {
89		Some((cb, leaf)) => (cb, leaf),
90		None => {
91			return Err(PsetError::MissingSimplicityLeaf {
92				cmr: cmr.to_string(),
93			});
94		}
95	};
96
97	let tx = pset.extract_tx().map_err(PsetError::PsetExtract)?;
98	let tx = Arc::new(tx);
99
100	let input_utxos = pset
101		.inputs()
102		.iter()
103		.enumerate()
104		.map(|(n, input)| match input.witness_utxo {
105			Some(ref utxo) => Ok(ElementsUtxo {
106				script_pubkey: utxo.script_pubkey.clone(),
107				asset: utxo.asset,
108				value: utxo.value,
109			}),
110			None => Err(PsetError::MissingWitnessUtxo(n)),
111		})
112		.collect::<Result<Vec<_>, _>>()?;
113
114	let tx_env = ElementsEnv::new(
115		tx,
116		input_utxos,
117		input_idx as u32, // cast fine, input indices are always small
118		cmr,
119		control_block.clone(),
120		None, // FIXME populate this; needs https://github.com/BlockstreamResearch/rust-simplicity/issues/315 first
121		genesis_hash,
122	);
123
124	Ok((tx_env, control_block, tap_leaf))
125}