1use std::iter;
2use std::sync::Arc;
3
4use dyn_clone::DynClone;
5
6use simplicityhl::CompiledProgram;
7use simplicityhl::elements::pset::PartiallySignedTransaction;
8use simplicityhl::elements::{Address, Script, Transaction, TxOut, taproot};
9use simplicityhl::simplicity::bitcoin::{XOnlyPublicKey, secp256k1};
10use simplicityhl::simplicity::jet::Elements;
11use simplicityhl::simplicity::jet::elements::{ElementsEnv, ElementsUtxo};
12use simplicityhl::simplicity::{BitMachine, RedeemNode, Value, leaf_version};
13use simplicityhl::tracker::DefaultTracker;
14use simplicityhl::{Parameters, WitnessTypes, WitnessValues};
15
16use crate::global::get_log_level;
17
18use super::arguments::ArgumentsTrait;
19use super::error::ProgramError;
20
21use crate::provider::SimplicityNetwork;
22use crate::utils::{hash_script, tap_data_hash, tr_unspendable_key};
23
24pub trait ProgramTrait: DynClone {
28 fn get_argument_types(&self) -> Result<Parameters, ProgramError>;
33
34 fn get_witness_types(&self) -> Result<WitnessTypes, ProgramError>;
39
40 fn get_env(
45 &self,
46 pst: &PartiallySignedTransaction,
47 input_index: usize,
48 network: &SimplicityNetwork,
49 ) -> Result<ElementsEnv<Arc<Transaction>>, ProgramError>;
50
51 fn execute(
60 &self,
61 pst: &PartiallySignedTransaction,
62 witness: &WitnessValues,
63 input_index: usize,
64 network: &SimplicityNetwork,
65 ) -> Result<(Arc<RedeemNode<Elements>>, Value), ProgramError>;
66
67 fn finalize(
72 &self,
73 pst: &PartiallySignedTransaction,
74 witness: &WitnessValues,
75 input_index: usize,
76 network: &SimplicityNetwork,
77 ) -> Result<Vec<Vec<u8>>, ProgramError>;
78}
79
80#[derive(Clone)]
84pub struct Program {
85 source: &'static str,
86 pub_key: XOnlyPublicKey,
87 arguments: Box<dyn ArgumentsTrait>,
88 storage: Vec<[u8; 32]>,
89}
90
91dyn_clone::clone_trait_object!(ProgramTrait);
92
93impl ProgramTrait for Program {
94 fn get_argument_types(&self) -> Result<Parameters, ProgramError> {
95 self.get_argument_types()
96 }
97
98 fn get_witness_types(&self) -> Result<WitnessTypes, ProgramError> {
99 self.get_witness_types()
100 }
101
102 fn get_env(
103 &self,
104 pst: &PartiallySignedTransaction,
105 input_index: usize,
106 network: &SimplicityNetwork,
107 ) -> Result<ElementsEnv<Arc<Transaction>>, ProgramError> {
108 let genesis_hash = network.genesis_block_hash();
109 let cmr = self.load()?.commit().cmr();
110 let utxos: Vec<TxOut> = pst.inputs().iter().filter_map(|x| x.witness_utxo.clone()).collect();
111
112 if utxos.len() <= input_index {
113 return Err(ProgramError::UtxoIndexOutOfBounds {
114 input_index,
115 utxo_count: utxos.len(),
116 });
117 }
118
119 let target_utxo = &utxos[input_index];
120 let script_pubkey = self.get_tr_address(network).script_pubkey();
121
122 if target_utxo.script_pubkey != script_pubkey {
123 return Err(ProgramError::ScriptPubkeyMismatch {
124 expected_hash: script_pubkey.script_hash().to_string(),
125 actual_hash: target_utxo.script_pubkey.script_hash().to_string(),
126 });
127 }
128
129 Ok(ElementsEnv::new(
130 Arc::new(pst.extract_tx()?),
131 utxos
132 .iter()
133 .map(|utxo| ElementsUtxo {
134 script_pubkey: utxo.script_pubkey.clone(),
135 asset: utxo.asset,
136 value: utxo.value,
137 })
138 .collect(),
139 u32::try_from(input_index)?,
140 cmr,
141 self.control_block()?,
142 None,
143 genesis_hash,
144 ))
145 }
146
147 fn execute(
148 &self,
149 pst: &PartiallySignedTransaction,
150 witness: &WitnessValues,
151 input_index: usize,
152 network: &SimplicityNetwork,
153 ) -> Result<(Arc<RedeemNode<Elements>>, Value), ProgramError> {
154 let satisfied = self
155 .load()?
156 .satisfy(witness.clone())
157 .map_err(ProgramError::WitnessSatisfaction)?;
158
159 let mut tracker = DefaultTracker::new(satisfied.debug_symbols()).with_log_level(get_log_level());
160
161 let env = self.get_env(pst, input_index, network)?;
162
163 let pruned = satisfied.redeem().prune_with_tracker(&env, &mut tracker)?;
164 let mut mac = BitMachine::for_program(&pruned)?;
165
166 let result = mac.exec(&pruned, &env)?;
167
168 Ok((pruned, result))
169 }
170
171 fn finalize(
172 &self,
173 pst: &PartiallySignedTransaction,
174 witness: &WitnessValues,
175 input_index: usize,
176 network: &SimplicityNetwork,
177 ) -> Result<Vec<Vec<u8>>, ProgramError> {
178 let pruned = self.execute(pst, witness, input_index, network)?.0;
179
180 let (simplicity_program_bytes, simplicity_witness_bytes) = pruned.to_vec_with_witness();
181 let cmr = pruned.cmr();
182
183 Ok(vec![
184 simplicity_witness_bytes,
185 simplicity_program_bytes,
186 cmr.as_ref().to_vec(),
187 self.control_block()?.serialize(),
188 ])
189 }
190}
191
192impl Program {
193 #[must_use]
195 pub fn new(source: &'static str, arguments: Box<dyn ArgumentsTrait>) -> Self {
196 Self {
197 source,
198 pub_key: tr_unspendable_key(),
199 arguments,
200 storage: Vec::new(),
201 }
202 }
203
204 #[must_use]
207 pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self {
208 self.pub_key = pub_key;
209
210 self
211 }
212
213 #[must_use]
215 pub fn with_storage_capacity(mut self, capacity: usize) -> Self {
216 self.storage = vec![[0u8; 32]; capacity];
217
218 self
219 }
220
221 pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) {
226 let slot = self.storage.get_mut(index).expect("Index out of bounds");
227
228 *slot = new_value;
229 }
230
231 #[must_use]
233 pub fn get_storage_len(&self) -> usize {
234 self.storage.len()
235 }
236
237 #[must_use]
239 pub fn get_storage(&self) -> &[[u8; 32]] {
240 &self.storage
241 }
242
243 #[must_use]
248 pub fn get_storage_at(&self, index: usize) -> [u8; 32] {
249 self.storage[index]
250 }
251
252 #[must_use]
257 pub fn get_tr_address(&self, network: &SimplicityNetwork) -> Address {
258 let spend_info = self.taproot_spending_info().unwrap();
259
260 Address::p2tr(
261 secp256k1::SECP256K1,
262 spend_info.internal_key(),
263 spend_info.merkle_root(),
264 None,
265 network.address_params(),
266 )
267 }
268
269 #[must_use]
271 pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script {
272 self.get_tr_address(network).script_pubkey()
273 }
274
275 #[must_use]
277 pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] {
278 hash_script(&self.get_script_pubkey(network))
279 }
280
281 pub fn get_argument_types(&self) -> Result<Parameters, ProgramError> {
286 let compiled = self.load()?;
287 let abi_meta = compiled.generate_abi_meta().map_err(ProgramError::ProgramGenAbiMeta)?;
288
289 Ok(abi_meta.param_types)
290 }
291
292 pub fn get_witness_types(&self) -> Result<WitnessTypes, ProgramError> {
297 let compiled = self.load()?;
298 let abi_meta = compiled.generate_abi_meta().map_err(ProgramError::ProgramGenAbiMeta)?;
299
300 Ok(abi_meta.witness_types)
301 }
302
303 fn load(&self) -> Result<CompiledProgram, ProgramError> {
304 let compiled = CompiledProgram::new(self.source, self.arguments.build_arguments(), true)
305 .map_err(ProgramError::Compilation)?;
306 Ok(compiled)
307 }
308
309 fn script_version(&self) -> Result<(Script, taproot::LeafVersion), ProgramError> {
310 let cmr = self.load()?.commit().cmr();
311 let script = Script::from(cmr.as_ref().to_vec());
312
313 Ok((script, leaf_version()))
314 }
315
316 fn taproot_leaf_depths(total_leaves: usize) -> Vec<usize> {
317 assert!(total_leaves > 0, "Taproot tree must contain at least one leaf");
318
319 let next_pow2 = total_leaves.next_power_of_two();
320 let depth = next_pow2.ilog2() as usize;
321
322 let shallow_count = next_pow2 - total_leaves;
323 let deep_count = total_leaves - shallow_count;
324
325 let mut depths = Vec::with_capacity(total_leaves);
326 depths.extend(iter::repeat_n(depth, deep_count));
327
328 if depth > 0 {
329 depths.extend(iter::repeat_n(depth - 1, shallow_count));
330 }
331
332 depths
333 }
334
335 fn taproot_spending_info(&self) -> Result<taproot::TaprootSpendInfo, ProgramError> {
336 let mut builder = taproot::TaprootBuilder::new();
337 let (script, version) = self.script_version()?;
338 let depths = Self::taproot_leaf_depths(1 + self.get_storage_len());
339
340 builder = builder
341 .add_leaf_with_ver(depths[0], script, version)
342 .expect("tap tree should be valid");
343
344 for (slot, depth) in self.get_storage().iter().zip(depths.into_iter().skip(1)) {
345 builder = builder
346 .add_hidden(depth, tap_data_hash(slot))
347 .expect("tap tree should be valid");
348 }
349
350 Ok(builder
351 .finalize(secp256k1::SECP256K1, self.pub_key)
352 .expect("tap tree should be valid"))
353 }
354
355 fn control_block(&self) -> Result<taproot::ControlBlock, ProgramError> {
356 let info = self.taproot_spending_info()?;
357 let script_ver = self.script_version()?;
358
359 Ok(info.control_block(&script_ver).expect("control block should exist"))
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use simplicityhl::{
366 Arguments,
367 elements::{AssetId, confidential, pset::Input},
368 };
369
370 use super::*;
371
372 const DUMMY_PROGRAM: &str = r"
374 fn main() {
375 let ab: u16 = <(u8, u8)>::into((0x10, 0x01));
376 let c: u16 = 0x1001;
377 assert!(jet::eq_16(ab, c));
378 let ab: u8 = <(u4, u4)>::into((0b1011, 0b1101));
379 let c: u8 = 0b10111101;
380 assert!(jet::eq_8(ab, c));
381 }
382 ";
383
384 #[derive(Clone)]
385 struct EmptyArguments;
386
387 impl ArgumentsTrait for EmptyArguments {
388 fn build_arguments(&self) -> Arguments {
389 Arguments::default()
390 }
391 }
392
393 fn dummy_asset_id(byte: u8) -> AssetId {
394 AssetId::from_slice(&[byte; 32]).unwrap()
395 }
396
397 fn dummy_program() -> Program {
398 Program::new(DUMMY_PROGRAM, Box::new(EmptyArguments))
399 }
400
401 fn dummy_network() -> SimplicityNetwork {
402 SimplicityNetwork::default_regtest()
403 }
404
405 fn make_pst_with_script(script: Script) -> PartiallySignedTransaction {
406 let txout = TxOut {
407 asset: confidential::Asset::Explicit(dummy_asset_id(0xAA)),
408 value: confidential::Value::Explicit(1000),
409 script_pubkey: script,
410 ..Default::default()
411 };
412 let input = Input {
413 witness_utxo: Some(txout),
414 ..Default::default()
415 };
416
417 let mut pst = PartiallySignedTransaction::new_v2();
418
419 pst.add_input(input);
420
421 pst
422 }
423
424 #[test]
425 fn test_get_env_idx() {
426 let program = dummy_program();
427 let network = dummy_network();
428
429 let correct_script = program.get_script_pubkey(&network);
430 let wrong_script = Script::new();
431
432 let mut pst = make_pst_with_script(wrong_script);
433
434 let correct_txout = TxOut {
435 asset: confidential::Asset::Explicit(dummy_asset_id(0xAA)),
436 value: confidential::Value::Explicit(1000),
437 script_pubkey: correct_script,
438 ..Default::default()
439 };
440
441 pst.add_input(Input {
442 witness_utxo: Some(correct_txout),
443 ..Default::default()
444 });
445
446 assert!(matches!(
448 program.get_env(&pst, 0, &network).unwrap_err(),
449 ProgramError::ScriptPubkeyMismatch { .. }
450 ));
451
452 assert!(program.get_env(&pst, 1, &network).is_ok());
453 }
454
455 #[test]
456 fn test_taproot_leaf_depths_known_values() {
457 let cases = [
458 (1, vec![0]),
459 (2, vec![1, 1]),
460 (3, vec![2, 2, 1]),
461 (4, vec![2, 2, 2, 2]),
462 (5, vec![3, 3, 2, 2, 2]),
463 (6, vec![3, 3, 3, 3, 2, 2]),
464 (8, vec![3, 3, 3, 3, 3, 3, 3, 3]),
465 ];
466
467 for (n, expected) in cases {
468 assert_eq!(Program::taproot_leaf_depths(n), expected, "n={n}");
469 }
470 }
471}