1use std::collections::{HashMap, HashSet};
2use std::str::FromStr;
3
4use elements_miniscript::Descriptor;
5use elements_miniscript::bitcoin::PublicKey;
6use elements_miniscript::descriptor::Wpkh;
7
8use simplicityhl::Value;
9use simplicityhl::WitnessValues;
10use simplicityhl::elements::pset::PartiallySignedTransaction;
11use simplicityhl::elements::secp256k1_zkp::{All, Keypair, Message, Secp256k1, ecdsa, schnorr};
12use simplicityhl::elements::{Address, AssetId, OutPoint, Script, Transaction, TxOut, Txid};
13use simplicityhl::simplicity::bitcoin::XOnlyPublicKey;
14use simplicityhl::simplicity::hashes::Hash;
15use simplicityhl::str::WitnessName;
16use simplicityhl::value::ValueConstructible;
17
18use bip39::Mnemonic;
19
20use elements_miniscript::{
21 DescriptorPublicKey,
22 bitcoin::{NetworkKind, PrivateKey, bip32::DerivationPath},
23 elements::{
24 EcdsaSighashType,
25 bitcoin::bip32::{Fingerprint, Xpriv, Xpub},
26 sighash::SighashCache,
27 },
28 elementssig_to_rawsig,
29 psbt::PsbtExt,
30};
31
32use super::error::SignerError;
33use crate::constants::MIN_FEE;
34use crate::program::ProgramTrait;
35use crate::provider::ProviderTrait;
36use crate::provider::SimplicityNetwork;
37use crate::transaction::FinalTransaction;
38use crate::transaction::PartialInput;
39use crate::transaction::PartialOutput;
40use crate::transaction::RequiredSignature;
41
42pub const PLACEHOLDER_FEE: u64 = 1;
43
44pub trait SignerTrait {
45 fn sign_program(
46 &self,
47 pst: &PartiallySignedTransaction,
48 program: &dyn ProgramTrait,
49 input_index: usize,
50 network: &SimplicityNetwork,
51 ) -> Result<schnorr::Signature, SignerError>;
52
53 fn sign_input(
54 &self,
55 pst: &PartiallySignedTransaction,
56 input_index: usize,
57 ) -> Result<(PublicKey, ecdsa::Signature), SignerError>;
58}
59
60pub struct Signer {
61 xprv: Xpriv,
62 provider: Box<dyn ProviderTrait>,
63 network: SimplicityNetwork,
64 secp: Secp256k1<All>,
65}
66
67impl SignerTrait for Signer {
68 fn sign_program(
69 &self,
70 pst: &PartiallySignedTransaction,
71 program: &dyn ProgramTrait,
72 input_index: usize,
73 network: &SimplicityNetwork,
74 ) -> Result<schnorr::Signature, SignerError> {
75 let env = program.get_env(pst, input_index, network)?;
76 let msg = Message::from_digest(env.c_tx_env().sighash_all().to_byte_array());
77
78 let private_key = self.get_private_key()?;
79 let keypair = Keypair::from_secret_key(&self.secp, &private_key.inner);
80
81 Ok(self.secp.sign_schnorr(&msg, &keypair))
82 }
83
84 fn sign_input(
85 &self,
86 pst: &PartiallySignedTransaction,
87 input_index: usize,
88 ) -> Result<(PublicKey, ecdsa::Signature), SignerError> {
89 let tx = pst.extract_tx()?;
90
91 let mut sighash_cache = SighashCache::new(&tx);
92 let genesis_hash = elements_miniscript::elements::BlockHash::all_zeros();
93
94 let message = pst
95 .sighash_msg(input_index, &mut sighash_cache, None, genesis_hash)?
96 .to_secp_msg();
97
98 let private_key = self.get_private_key()?;
99 let public_key = private_key.public_key(&self.secp);
100
101 let signature = self.secp.sign_ecdsa_low_r(&message, &private_key.inner);
102
103 Ok((public_key, signature))
104 }
105}
106
107enum Estimate {
108 Success(Transaction, u64),
109 Failure(u64),
110}
111
112impl Signer {
113 pub fn new(mnemonic: &str, provider: Box<dyn ProviderTrait>) -> Result<Self, SignerError> {
114 let secp = Secp256k1::new();
115 let mnemonic: Mnemonic = mnemonic
116 .parse()
117 .map_err(|e: bip39::Error| SignerError::Mnemonic(e.to_string()))?;
118 let seed = mnemonic.to_seed("");
119 let xprv = Xpriv::new_master(NetworkKind::Test, &seed)?;
120
121 let network = *provider.get_network();
122
123 Ok(Self {
124 xprv,
125 provider,
126 network,
127 secp,
128 })
129 }
130
131 pub fn send(&self, to: Script, amount: u64) -> Result<(Transaction, u64), SignerError> {
133 let mut ft = FinalTransaction::new(self.network);
134
135 ft.add_output(PartialOutput::new(to, amount, self.network.policy_asset()));
136
137 self.finalize(&ft)
138 }
139
140 pub fn finalize(&self, tx: &FinalTransaction) -> Result<(Transaction, u64), SignerError> {
141 let mut signer_utxos = self.get_wpkh_utxos_asset(self.network.policy_asset())?;
142 let mut set = HashSet::new();
143
144 for input in tx.inputs() {
145 set.insert(OutPoint {
146 txid: input.partial_input.witness_txid,
147 vout: input.partial_input.witness_output_index,
148 });
149 }
150
151 signer_utxos.retain(|(outpoint, _)| !set.contains(outpoint));
152 signer_utxos.sort_by(|a, b| b.1.value.cmp(&a.1.value));
153
154 let mut fee_tx = tx.clone();
155 let mut curr_fee = MIN_FEE;
156 let fee_rate = self.provider.fetch_fee_rate(1)?;
157
158 for utxo in signer_utxos {
159 let policy_amount_delta = fee_tx.calculate_fee_delta();
160
161 if policy_amount_delta >= curr_fee as i64 {
162 match self.estimate_tx(fee_tx.clone(), fee_rate, policy_amount_delta as u64)? {
163 Estimate::Success(tx, fee) => return Ok((tx, fee)),
164 Estimate::Failure(required_fee) => curr_fee = required_fee,
165 }
166 }
167
168 fee_tx.add_input(PartialInput::new(utxo.0, utxo.1), RequiredSignature::NativeEcdsa)?;
169 }
170
171 let policy_amount_delta = fee_tx.calculate_fee_delta();
173
174 if policy_amount_delta >= curr_fee as i64 {
175 match self.estimate_tx(fee_tx.clone(), fee_rate, policy_amount_delta as u64)? {
176 Estimate::Success(tx, fee) => return Ok((tx, fee)),
177 Estimate::Failure(required_fee) => curr_fee = required_fee,
178 }
179 }
180
181 Err(SignerError::NotEnoughFunds(curr_fee))
182 }
183
184 pub fn finalize_strict(
185 &self,
186 tx: &FinalTransaction,
187 target_blocks: u32,
188 ) -> Result<(Transaction, u64), SignerError> {
189 let policy_amount_delta = tx.calculate_fee_delta();
190
191 if policy_amount_delta < MIN_FEE as i64 {
192 return Err(SignerError::DustAmount(policy_amount_delta));
193 }
194
195 let fee_rate = self.provider.fetch_fee_rate(target_blocks)?;
196
197 match self.estimate_tx(tx.clone(), fee_rate, policy_amount_delta as u64)? {
199 Estimate::Success(tx, fee) => Ok((tx, fee)),
200 Estimate::Failure(required_fee) => Err(SignerError::NotEnoughFeeAmount(policy_amount_delta, required_fee)),
201 }
202 }
203
204 pub fn get_provider(&self) -> &dyn ProviderTrait {
205 self.provider.as_ref()
206 }
207
208 pub fn get_wpkh_address(&self) -> Result<Address, SignerError> {
209 let fingerprint = self.fingerprint()?;
210 let path = self.get_derivation_path()?;
211 let xpub = self.derive_xpub(&path)?;
212
213 let desc = format!("elwpkh([{fingerprint}/{path}]{xpub}/<0;1>/*)");
214
215 let descriptor: Descriptor<DescriptorPublicKey> =
216 Descriptor::Wpkh(Wpkh::from_str(&desc).map_err(|e| SignerError::WpkhDescriptor(e.to_string()))?);
217
218 Ok(descriptor.clone().into_single_descriptors()?[0]
219 .at_derivation_index(1)?
220 .address(self.network.address_params())?)
221 }
222
223 pub fn get_wpkh_utxos(&self) -> Result<Vec<(OutPoint, TxOut)>, SignerError> {
224 self.get_wpkh_utxos_filter(|_| true)
225 }
226
227 pub fn get_wpkh_utxos_asset(&self, asset: AssetId) -> Result<Vec<(OutPoint, TxOut)>, SignerError> {
228 self.get_wpkh_utxos_filter(|(_, txout)| txout.asset.explicit().unwrap() == asset)
229 }
230
231 pub fn get_wpkh_utxos_txid(&self, txid: Txid) -> Result<Vec<(OutPoint, TxOut)>, SignerError> {
233 self.get_wpkh_utxos_filter(|(outpoint, _)| outpoint.txid == txid)
234 }
235
236 pub fn get_wpkh_utxos_filter<F>(&self, filter: F) -> Result<Vec<(OutPoint, TxOut)>, SignerError>
237 where
238 F: FnMut(&(OutPoint, TxOut)) -> bool,
239 {
240 let mut utxos = self.provider.fetch_address_utxos(&self.get_wpkh_address()?)?;
241
242 utxos.retain(filter);
243
244 Ok(utxos)
245 }
246
247 pub fn get_schnorr_public_key(&self) -> Result<XOnlyPublicKey, SignerError> {
248 let private_key = self.get_private_key()?;
249 let keypair = Keypair::from_secret_key(&self.secp, &private_key.inner);
250
251 Ok(keypair.x_only_public_key().0)
252 }
253
254 pub fn get_ecdsa_public_key(&self) -> Result<PublicKey, SignerError> {
255 Ok(self.get_private_key()?.public_key(&self.secp))
256 }
257
258 pub fn get_private_key(&self) -> Result<PrivateKey, SignerError> {
259 let master_xprv = self.master_xpriv()?;
260 let full_path = self.get_derivation_path()?;
261
262 let derived =
263 full_path.extend(DerivationPath::from_str("0/1").map_err(|e| SignerError::DerivationPath(e.to_string()))?);
264
265 let ext_derived = master_xprv.derive_priv(&self.secp, &derived)?;
266
267 Ok(PrivateKey::new(ext_derived.private_key, NetworkKind::Test))
268 }
269
270 fn estimate_tx(
271 &self,
272 mut fee_tx: FinalTransaction,
273 fee_rate: f32,
274 available_delta: u64,
275 ) -> Result<Estimate, SignerError> {
276 fee_tx.add_output(PartialOutput::new(
279 self.get_wpkh_address()?.script_pubkey(),
280 PLACEHOLDER_FEE,
281 self.network.policy_asset(),
282 ));
283
284 fee_tx.add_output(PartialOutput::new(
285 Script::new(),
286 PLACEHOLDER_FEE,
287 self.network.policy_asset(),
288 ));
289
290 let final_tx = self.sign_tx(&fee_tx)?;
291 let fee = fee_tx.calculate_fee(final_tx.weight(), fee_rate);
292
293 if available_delta > fee && available_delta - fee >= MIN_FEE {
294 let outputs = fee_tx.outputs_mut();
296
297 outputs[outputs.len() - 2].amount = available_delta - fee;
298 outputs[outputs.len() - 1].amount = fee;
299
300 let final_tx = self.sign_tx(&fee_tx)?;
301
302 return Ok(Estimate::Success(final_tx, fee));
303 }
304
305 fee_tx.remove_output(fee_tx.n_outputs() - 2);
307
308 let final_tx = self.sign_tx(&fee_tx)?;
309 let fee = fee_tx.calculate_fee(final_tx.weight(), fee_rate);
310
311 if available_delta < fee {
312 return Ok(Estimate::Failure(fee));
313 }
314
315 let outputs = fee_tx.outputs_mut();
316
317 outputs[outputs.len() - 1].amount = available_delta;
319
320 let final_tx = self.sign_tx(&fee_tx)?;
322
323 Ok(Estimate::Success(final_tx, fee))
324 }
325
326 fn sign_tx(&self, tx: &FinalTransaction) -> Result<Transaction, SignerError> {
327 let mut pst = tx.extract_pst();
328 let inputs = tx.inputs();
329
330 for (index, input_i) in inputs.iter().enumerate() {
331 if let Some(program_input) = &input_i.program_input {
333 let signed_witness: Result<WitnessValues, SignerError> = match &input_i.required_sig {
334 RequiredSignature::Witness(witness_name) => Ok(self.get_signed_program_witness(
336 &pst,
337 program_input.program.as_ref(),
338 &program_input.witness.build_witness(),
339 witness_name,
340 index,
341 )?),
342 _ => Ok(program_input.witness.build_witness()),
344 };
345 let pruned_witness = program_input
346 .program
347 .finalize(&pst, &signed_witness.unwrap(), index, &self.network)
348 .unwrap();
349
350 pst.inputs_mut()[index].final_script_witness = Some(pruned_witness);
351 } else {
352 let signed_witness = self.sign_input(&pst, index)?;
355 let raw_sig = elementssig_to_rawsig(&(signed_witness.1, EcdsaSighashType::All));
356
357 pst.inputs_mut()[index].final_script_witness = Some(vec![raw_sig, signed_witness.0.to_bytes()]);
358 }
359 }
360
361 Ok(pst.extract_tx()?)
362 }
363
364 fn get_signed_program_witness(
365 &self,
366 pst: &PartiallySignedTransaction,
367 program: &dyn ProgramTrait,
368 witness: &WitnessValues,
369 witness_name: &str,
370 index: usize,
371 ) -> Result<WitnessValues, SignerError> {
372 let signature = self.sign_program(pst, program, index, &self.network)?;
373
374 let mut hm = HashMap::new();
375
376 witness.iter().for_each(|el| {
377 hm.insert(el.0.clone(), el.1.clone());
378 });
379
380 hm.insert(
381 WitnessName::from_str_unchecked(witness_name),
382 Value::byte_array(signature.serialize()),
383 );
384
385 Ok(WitnessValues::from(hm))
386 }
387
388 fn derive_xpriv(&self, path: &DerivationPath) -> Result<Xpriv, SignerError> {
389 Ok(self.xprv.derive_priv(&self.secp, &path)?)
390 }
391
392 fn master_xpriv(&self) -> Result<Xpriv, SignerError> {
393 self.derive_xpriv(&DerivationPath::master())
394 }
395
396 fn derive_xpub(&self, path: &DerivationPath) -> Result<Xpub, SignerError> {
397 let derived = self.derive_xpriv(path)?;
398
399 Ok(Xpub::from_priv(&self.secp, &derived))
400 }
401
402 fn master_xpub(&self) -> Result<Xpub, SignerError> {
403 self.derive_xpub(&DerivationPath::master())
404 }
405
406 fn fingerprint(&self) -> Result<Fingerprint, SignerError> {
407 Ok(self.master_xpub()?.fingerprint())
408 }
409
410 fn get_derivation_path(&self) -> Result<DerivationPath, SignerError> {
411 let coin_type = if self.network.is_mainnet() { 1776 } else { 1 };
412 let path = format!("84h/{coin_type}h/0h");
413
414 DerivationPath::from_str(&format!("m/{path}")).map_err(|e| SignerError::DerivationPath(e.to_string()))
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use crate::provider::EsploraProvider;
421
422 use super::*;
423
424 #[test]
425 fn keys_correspond_to_address() {
426 let url = "https://blockstream.info/liquidtestnet/api".to_string();
427 let network = SimplicityNetwork::LiquidTestnet;
428
429 let signer = Signer::new(
430 "exist carry drive collect lend cereal occur much tiger just involve mean",
431 Box::new(EsploraProvider::new(url, network)),
432 )
433 .unwrap();
434
435 let address = signer.get_wpkh_address().unwrap();
436 let pubkey = signer.get_ecdsa_public_key().unwrap();
437
438 let derived_addr = Address::p2wpkh(&pubkey, None, network.address_params());
439
440 assert_eq!(derived_addr.to_string(), address.to_string());
441 }
442}