hal_simplicity/actions/simplicity/
sighash.rs1use crate::simplicity::bitcoin::secp256k1::{
2 schnorr, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey,
3};
4use crate::simplicity::elements;
5use crate::simplicity::elements::hashes::sha256;
6use crate::simplicity::elements::hex::FromHex;
7
8use crate::simplicity::jet::elements::ElementsUtxo;
9use crate::simplicity::Cmr;
10
11use elements::bitcoin::secp256k1;
12use elements::hashes::Hash as _;
13use elements::pset::PartiallySignedTransaction;
14use serde::Serialize;
15
16use crate::simplicity::elements::taproot::ControlBlock;
17use crate::simplicity::jet::elements::ElementsEnv;
18
19use crate::actions::simplicity::ParseElementsUtxoError;
20
21#[derive(Debug, thiserror::Error)]
22pub enum SimplicitySighashError {
23 #[error("failed extracting transaction from PSET: {0}")]
24 PsetExtraction(elements::pset::Error),
25
26 #[error("invalid transaction hex: {0}")]
27 TransactionHexParsing(elements::hex::Error),
28
29 #[error("invalid transaction decoding: {0}")]
30 TransactionDecoding(elements::encode::Error),
31
32 #[error("invalid input index: {0}")]
33 InputIndexParsing(std::num::ParseIntError),
34
35 #[error("invalid CMR: {0}")]
36 CmrParsing(elements::hashes::hex::HexToArrayError),
37
38 #[error("invalid control block hex: {0}")]
39 ControlBlockHexParsing(elements::hex::Error),
40
41 #[error("invalid control block decoding: {0}")]
42 ControlBlockDecoding(elements::taproot::TaprootError),
43
44 #[error("input index {index} out-of-range for PSET with {n_inputs} inputs")]
45 InputIndexOutOfRange {
46 index: u32,
47 n_inputs: usize,
48 },
49
50 #[error("could not find control block in PSET for CMR {cmr}")]
51 ControlBlockNotFound {
52 cmr: String,
53 },
54
55 #[error("with a raw transaction, control-block must be provided")]
56 ControlBlockRequired,
57
58 #[error("witness UTXO field not populated for input {input}")]
59 WitnessUtxoMissing {
60 input: usize,
61 },
62
63 #[error("with a raw transaction, input-utxos must be provided")]
64 InputUtxosRequired,
65
66 #[error("expected {expected} input UTXOs but got {actual}")]
67 InputUtxoCountMismatch {
68 expected: usize,
69 actual: usize,
70 },
71
72 #[error("invalid genesis hash: {0}")]
73 GenesisHashParsing(elements::hashes::hex::HexToArrayError),
74
75 #[error("invalid secret key: {0}")]
76 SecretKeyParsing(secp256k1::Error),
77
78 #[error("secret key had public key {derived}, but was passed explicit public key {provided}")]
79 PublicKeyMismatch {
80 derived: String,
81 provided: String,
82 },
83
84 #[error("invalid public key: {0}")]
85 PublicKeyParsing(secp256k1::Error),
86
87 #[error("invalid signature: {0}")]
88 SignatureParsing(secp256k1::Error),
89
90 #[error("if signature is provided, public-key must be provided as well")]
91 SignatureWithoutPublicKey,
92
93 #[error("invalid input UTXO: {0}")]
94 InputUtxoParsing(ParseElementsUtxoError),
95}
96
97#[derive(Serialize)]
98pub struct SighashInfo {
99 pub sighash: sha256::Hash,
100 pub signature: Option<schnorr::Signature>,
101 pub valid_signature: Option<bool>,
102}
103
104#[allow(clippy::too_many_arguments)]
106pub fn simplicity_sighash(
107 tx_hex: &str,
108 input_idx: &str,
109 cmr: &str,
110 control_block: Option<&str>,
111 genesis_hash: Option<&str>,
112 secret_key: Option<&str>,
113 public_key: Option<&str>,
114 signature: Option<&str>,
115 input_utxos: Option<&[&str]>,
116) -> Result<SighashInfo, SimplicitySighashError> {
117 let secp = Secp256k1::new();
118
119 let pset = tx_hex.parse::<PartiallySignedTransaction>().ok();
123
124 let tx = match pset {
128 Some(ref pset) => pset.extract_tx().map_err(SimplicitySighashError::PsetExtraction)?,
129 None => {
130 let tx_bytes =
131 Vec::from_hex(tx_hex).map_err(SimplicitySighashError::TransactionHexParsing)?;
132 elements::encode::deserialize(&tx_bytes)
133 .map_err(SimplicitySighashError::TransactionDecoding)?
134 }
135 };
136 let input_idx: u32 = input_idx.parse().map_err(SimplicitySighashError::InputIndexParsing)?;
137 let cmr: Cmr = cmr.parse().map_err(SimplicitySighashError::CmrParsing)?;
138
139 let control_block = if let Some(cb) = control_block {
141 let cb_bytes = Vec::from_hex(cb).map_err(SimplicitySighashError::ControlBlockHexParsing)?;
142 ControlBlock::from_slice(&cb_bytes).map_err(SimplicitySighashError::ControlBlockDecoding)?
145 } else if let Some(ref pset) = pset {
146 let n_inputs = pset.n_inputs();
147 let input = pset
148 .inputs()
149 .get(input_idx as usize) .ok_or(SimplicitySighashError::InputIndexOutOfRange {
151 index: input_idx,
152 n_inputs,
153 })?;
154
155 let mut control_block = None;
156 for (cb, script_ver) in &input.tap_scripts {
157 if script_ver.1 == simplicity::leaf_version() && &script_ver.0[..] == cmr.as_ref() {
158 control_block = Some(cb.clone());
159 }
160 }
161 match control_block {
162 Some(cb) => cb,
163 None => {
164 return Err(SimplicitySighashError::ControlBlockNotFound {
165 cmr: cmr.to_string(),
166 })
167 }
168 }
169 } else {
170 return Err(SimplicitySighashError::ControlBlockRequired);
171 };
172
173 let input_utxos = if let Some(input_utxos) = input_utxos {
174 input_utxos
175 .iter()
176 .map(|utxo_str| {
177 crate::actions::simplicity::parse_elements_utxo(utxo_str)
178 .map_err(SimplicitySighashError::InputUtxoParsing)
179 })
180 .collect::<Result<Vec<_>, SimplicitySighashError>>()?
181 } else if let Some(ref pset) = pset {
182 pset.inputs()
183 .iter()
184 .enumerate()
185 .map(|(n, input)| match input.witness_utxo {
186 Some(ref utxo) => Ok(ElementsUtxo {
187 script_pubkey: utxo.script_pubkey.clone(),
188 asset: utxo.asset,
189 value: utxo.value,
190 }),
191 None => Err(SimplicitySighashError::WitnessUtxoMissing {
192 input: n,
193 }),
194 })
195 .collect::<Result<Vec<_>, SimplicitySighashError>>()?
196 } else {
197 return Err(SimplicitySighashError::InputUtxosRequired);
198 };
199 if input_utxos.len() != tx.input.len() {
200 return Err(SimplicitySighashError::InputUtxoCountMismatch {
201 expected: tx.input.len(),
202 actual: input_utxos.len(),
203 });
204 }
205
206 let genesis_hash = match genesis_hash {
208 Some(s) => s.parse().map_err(SimplicitySighashError::GenesisHashParsing)?,
209 None => elements::BlockHash::from_byte_array([
210 0xc1, 0xb1, 0x6a, 0xe2, 0x4f, 0x24, 0x23, 0xae, 0xa2, 0xea, 0x34, 0x55, 0x22, 0x92,
212 0x79, 0x3b, 0x5b, 0x5e, 0x82, 0x99, 0x9a, 0x1e, 0xed, 0x81, 0xd5, 0x6a, 0xee, 0x52,
213 0x8e, 0xda, 0x71, 0xa7,
214 ]),
215 };
216
217 let tx_env = ElementsEnv::new(
218 &tx,
219 input_utxos,
220 input_idx,
221 cmr,
222 control_block,
223 None, genesis_hash,
225 );
226
227 let (pk, sig) = match (public_key, signature) {
228 (Some(pk), None) => (
229 Some(pk.parse::<XOnlyPublicKey>().map_err(SimplicitySighashError::PublicKeyParsing)?),
230 None,
231 ),
232 (Some(pk), Some(sig)) => (
233 Some(pk.parse::<XOnlyPublicKey>().map_err(SimplicitySighashError::PublicKeyParsing)?),
234 Some(
235 sig.parse::<schnorr::Signature>()
236 .map_err(SimplicitySighashError::SignatureParsing)?,
237 ),
238 ),
239 (None, Some(_)) => return Err(SimplicitySighashError::SignatureWithoutPublicKey),
240 (None, None) => (None, None),
241 };
242
243 let sighash = tx_env.c_tx_env().sighash_all();
244 let sighash_msg = Message::from_digest(sighash.to_byte_array()); Ok(SighashInfo {
246 sighash,
247 signature: match secret_key {
248 Some(sk) => {
249 let sk: SecretKey = sk.parse().map_err(SimplicitySighashError::SecretKeyParsing)?;
250 let keypair = Keypair::from_secret_key(&secp, &sk);
251
252 if let Some(ref pk) = pk {
253 if pk != &keypair.x_only_public_key().0 {
254 return Err(SimplicitySighashError::PublicKeyMismatch {
255 derived: keypair.x_only_public_key().0.to_string(),
256 provided: pk.to_string(),
257 });
258 }
259 }
260
261 Some(secp.sign_schnorr(&sighash_msg, &keypair))
262 }
263 None => None,
264 },
265 valid_signature: match (pk, sig) {
266 (Some(pk), Some(sig)) => Some(secp.verify_schnorr(&sig, &sighash_msg, &pk).is_ok()),
267 _ => None,
268 },
269 })
270}