simplicityhl_core/
lib.rs

1//! High-level helpers for building and executing Simplicity programs on Liquid.
2//!
3//! This crate provides:
4//! - Address derivation for P2TR Simplicity programs
5//! - Utilities to compile and run programs with optional logging
6//! - Helpers to finalize transactions with Simplicity script witnesses
7//! - Esplora integration and small conveniences around Elements types
8
9mod constants;
10mod explorer;
11mod runner;
12mod scripts;
13mod taproot_pubkey_gen;
14mod trackers;
15
16#[cfg(feature = "encoding")]
17pub mod encoding {
18    pub use bincode::{Decode, Encode};
19
20    pub trait Encodable {
21        fn encode(&self) -> anyhow::Result<Vec<u8>>
22        where
23            Self: Encode,
24        {
25            bincode::encode_to_vec(self, bincode::config::standard()).map_err(anyhow::Error::msg)
26        }
27
28        fn decode(buf: &[u8]) -> anyhow::Result<Self>
29        where
30            Self: Sized,
31            Self: Decode<()>,
32        {
33            Ok(bincode::decode_from_slice(buf, bincode::config::standard())?.0)
34        }
35
36        fn to_hex(&self) -> anyhow::Result<String>
37        where
38            Self: Encode,
39        {
40            Ok(hex::encode(Encodable::encode(self)?))
41        }
42
43        fn from_hex(hex: &str) -> anyhow::Result<Self>
44        where
45            Self: bincode::Decode<()>,
46        {
47            Encodable::decode(&hex::decode(hex).map_err(anyhow::Error::msg)?)
48        }
49    }
50}
51
52pub use constants::*;
53pub use explorer::*;
54pub use runner::*;
55pub use scripts::*;
56pub use taproot_pubkey_gen::*;
57pub use trackers::*;
58
59#[cfg(feature = "encoding")]
60pub use encoding::Encodable;
61
62use std::collections::HashMap;
63use std::sync::Arc;
64
65use simplicityhl::num::U256;
66use simplicityhl::simplicity::RedeemNode;
67use simplicityhl::simplicity::bitcoin::key::Keypair;
68use simplicityhl::simplicity::bitcoin::{XOnlyPublicKey, secp256k1};
69use simplicityhl::simplicity::elements::{Address, AddressParams, Transaction, TxInWitness, TxOut};
70use simplicityhl::simplicity::hashes::Hash;
71use simplicityhl::simplicity::jet::Elements;
72use simplicityhl::simplicity::jet::elements::{ElementsEnv, ElementsUtxo};
73use simplicityhl::str::WitnessName;
74use simplicityhl::value::ValueConstructible;
75use simplicityhl::{CompiledProgram, Value, elements};
76
77/// Embedded Simplicity source for a basic P2PK program used to sign a single input.
78pub const P2PK_SOURCE: &str = include_str!("source_simf/p2pk.simf");
79
80/// Construct a P2TR address for the embedded P2PK program and the provided public key.
81pub fn get_p2pk_address(
82    x_only_public_key: &XOnlyPublicKey,
83    params: &'static AddressParams,
84) -> anyhow::Result<Address> {
85    Ok(create_p2tr_address(
86        get_p2pk_program(x_only_public_key)?.commit().cmr(),
87        x_only_public_key,
88        params,
89    ))
90}
91
92/// Compile the embedded P2PK program with the given X-only public key as argument.
93pub fn get_p2pk_program(account_public_key: &XOnlyPublicKey) -> anyhow::Result<CompiledProgram> {
94    let arguments = simplicityhl::Arguments::from(HashMap::from([(
95        WitnessName::from_str_unchecked("PUBLIC_KEY"),
96        Value::u256(U256::from_byte_array(account_public_key.serialize())),
97    )]));
98
99    load_program(P2PK_SOURCE, arguments)
100}
101
102/// Execute the compiled P2PK program against the provided env, producing a pruned redeem node.
103pub fn execute_p2pk_program(
104    compiled_program: &CompiledProgram,
105    keypair: &Keypair,
106    env: ElementsEnv<Arc<Transaction>>,
107    runner_log_level: RunnerLogLevel,
108) -> anyhow::Result<Arc<RedeemNode<Elements>>> {
109    let sighash_all = secp256k1::Message::from_digest(env.c_tx_env().sighash_all().to_byte_array());
110
111    let witness_values = simplicityhl::WitnessValues::from(HashMap::from([(
112        WitnessName::from_str_unchecked("SIGNATURE"),
113        Value::byte_array(keypair.sign_schnorr(sighash_all).serialize()),
114    )]));
115
116    Ok(run_program(compiled_program, witness_values, env, runner_log_level)?.0)
117}
118
119/// Finalize the given transaction by attaching a Simplicity witness for the specified P2PK input.
120///
121/// Preconditions:
122/// - `utxos[input_index]` must match the P2PK address derived from `keypair` and program CMR.
123pub fn finalize_p2pk_transaction(
124    mut tx: Transaction,
125    utxos: &[TxOut],
126    keypair: &Keypair,
127    input_index: usize,
128    params: &'static AddressParams,
129    genesis_hash: elements::BlockHash,
130) -> anyhow::Result<Transaction> {
131    let p2pk_program = get_p2pk_program(&keypair.x_only_public_key().0)?;
132
133    let cmr = p2pk_program.commit().cmr();
134
135    assert!(
136        utxos.len() > input_index,
137        "UTXOs must be greater than input index"
138    );
139
140    let target_utxo = &utxos[input_index];
141    let script_pubkey =
142        create_p2tr_address(cmr, &keypair.x_only_public_key().0, params).script_pubkey();
143
144    assert_eq!(
145        target_utxo.script_pubkey, script_pubkey,
146        "Expected for the UTXO to be spent by P2PK to be owned by the user."
147    );
148
149    let env: ElementsEnv<Arc<Transaction>> = ElementsEnv::new(
150        Arc::new(tx.clone()),
151        utxos
152            .iter()
153            .map(|utxo| ElementsUtxo {
154                script_pubkey: utxo.script_pubkey.clone(),
155                asset: utxo.asset,
156                value: utxo.value,
157            })
158            .collect(),
159        input_index as u32,
160        cmr,
161        control_block(cmr, keypair.x_only_public_key().0),
162        None,
163        genesis_hash,
164    );
165
166    let pruned = execute_p2pk_program(&p2pk_program, keypair, env, RunnerLogLevel::None)?;
167
168    let (simplicity_program_bytes, simplicity_witness_bytes) = pruned.to_vec_with_witness();
169    let cmr = pruned.cmr();
170
171    tx.input[input_index].witness = TxInWitness {
172        amount_rangeproof: None,
173        inflation_keys_rangeproof: None,
174        script_witness: vec![
175            simplicity_witness_bytes,
176            simplicity_program_bytes,
177            cmr.as_ref().to_vec(),
178            control_block(cmr, keypair.x_only_public_key().0).serialize(),
179        ],
180        pegin_witness: vec![],
181    };
182
183    Ok(tx)
184}