hal_simplicity/actions/simplicity/pset/
update_input.rs1use core::str::FromStr;
5use std::collections::BTreeMap;
6
7use elements::bitcoin::secp256k1;
8use elements::schnorr::XOnlyPublicKey;
9use simplicity::hex::parse::FromHex as _;
10
11use crate::hal_simplicity::taproot_spend_info;
12
13use super::{PsetError, UpdatedPset};
14
15use crate::actions::simplicity::ParseElementsUtxoError;
16
17#[derive(Debug, thiserror::Error)]
18pub enum PsetUpdateInputError {
19 #[error(transparent)]
20 SharedError(#[from] PsetError),
21
22 #[error("invalid PSET: {0}")]
23 PsetDecode(elements::pset::ParseError),
24
25 #[error("invalid input index: {0}")]
26 InputIndexParse(std::num::ParseIntError),
27
28 #[error("input index {index} out-of-range for PSET with {total} inputs")]
29 InputIndexOutOfRange {
30 index: usize,
31 total: usize,
32 },
33
34 #[error("invalid CMR: {0}")]
35 CmrParse(elements::hashes::hex::HexToArrayError),
36
37 #[error("invalid internal key: {0}")]
38 InternalKeyParse(secp256k1::Error),
39
40 #[error("internal key must be present if CMR is; PSET requires a control block for each CMR, which in turn requires the internal key. If you don't know the internal key, good chance it is the BIP-0341 'unspendable key' 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 or the web IDE's 'unspendable key' (highly discouraged for use in production) of f5919fa64ce45f8306849072b26c1bfdd2937e6b81774796ff372bd1eb5362d2")]
41 MissingInternalKey,
42
43 #[error("input UTXO does not appear to be a Taproot output")]
44 NotTaprootOutput,
45
46 #[error("invalid state commitment: {0}")]
47 StateParse(elements::hashes::hex::HexToArrayError),
48
49 #[error("CMR and internal key imply output key {output_key}, which does not match input scriptPubKey {script_pubkey}")]
50 OutputKeyMismatch {
51 output_key: String,
52 script_pubkey: String,
53 },
54
55 #[error("invalid elements UTXO: {0}")]
56 ElementsUtxoParse(ParseElementsUtxoError),
57}
58
59pub fn pset_update_input(
61 pset_b64: &str,
62 input_idx: &str,
63 input_utxo: &str,
64 internal_key: Option<&str>,
65 cmr: Option<&str>,
66 state: Option<&str>,
67) -> Result<UpdatedPset, PsetUpdateInputError> {
68 let mut pset: elements::pset::PartiallySignedTransaction =
69 pset_b64.parse().map_err(PsetUpdateInputError::PsetDecode)?;
70 let input_idx: usize = input_idx.parse().map_err(PsetUpdateInputError::InputIndexParse)?;
71 let input_utxo = super::super::parse_elements_utxo(input_utxo)
72 .map_err(PsetUpdateInputError::ElementsUtxoParse)?;
73
74 let n_inputs = pset.n_inputs();
75 let input = pset.inputs_mut().get_mut(input_idx).ok_or_else(|| {
76 PsetUpdateInputError::InputIndexOutOfRange {
77 index: input_idx,
78 total: n_inputs,
79 }
80 })?;
81
82 let cmr =
83 cmr.map(simplicity::Cmr::from_str).transpose().map_err(PsetUpdateInputError::CmrParse)?;
84 let internal_key = internal_key
85 .map(XOnlyPublicKey::from_str)
86 .transpose()
87 .map_err(PsetUpdateInputError::InternalKeyParse)?;
88 if cmr.is_some() && internal_key.is_none() {
89 return Err(PsetUpdateInputError::MissingInternalKey);
90 }
91
92 if !input_utxo.script_pubkey.is_v1_p2tr() {
93 return Err(PsetUpdateInputError::NotTaprootOutput);
94 }
95
96 let state =
100 state.map(<[u8; 32]>::from_hex).transpose().map_err(PsetUpdateInputError::StateParse)?;
101
102 let mut updated_values = vec![];
103 if let Some(internal_key) = internal_key {
104 updated_values.push("tap_internal_key");
105 input.tap_internal_key = Some(internal_key);
106 if let Some(cmr) = cmr {
109 let spend_info = taproot_spend_info(internal_key, state, cmr);
113 if spend_info.output_key().as_inner().serialize() != input_utxo.script_pubkey[2..] {
114 return Err(PsetUpdateInputError::OutputKeyMismatch {
116 output_key: format!("{}", spend_info.output_key().as_inner()),
117 script_pubkey: format!("{}", input_utxo.script_pubkey),
118 });
119 }
120
121 let script_ver = spend_info.as_script_map().keys().next().unwrap();
123 let cb = spend_info.control_block(script_ver).unwrap();
124 input.tap_merkle_root = spend_info.merkle_root();
125 input.tap_scripts = BTreeMap::new();
126 input.tap_scripts.insert(cb, script_ver.clone());
127 updated_values.push("tap_merkle_root");
128 updated_values.push("tap_scripts");
129 }
130 }
131
132 input.witness_utxo = Some(elements::TxOut {
134 asset: input_utxo.asset,
135 value: input_utxo.value,
136 nonce: elements::confidential::Nonce::Null, script_pubkey: input_utxo.script_pubkey,
138 witness: elements::TxOutWitness::empty(), });
140 updated_values.push("witness_utxo");
141
142 Ok(UpdatedPset {
143 pset: pset.to_string(),
144 updated_values,
145 })
146}