Skip to main content

smplx_sdk/signer/
core.rs

1use std::collections::{HashMap, HashSet};
2use std::str::FromStr;
3use std::sync::Arc;
4
5use simplicityhl::Value;
6use simplicityhl::WitnessValues;
7use simplicityhl::elements::pset::PartiallySignedTransaction;
8use simplicityhl::elements::secp256k1_zkp::{All, Keypair, Message, Secp256k1, ecdsa, schnorr};
9use simplicityhl::elements::{Address, AssetId, OutPoint, Script, Transaction, Txid};
10use simplicityhl::simplicity::bitcoin::XOnlyPublicKey;
11use simplicityhl::simplicity::hashes::Hash;
12use simplicityhl::str::WitnessName;
13use simplicityhl::value::ValueConstructible;
14
15use bip39::Mnemonic;
16use bip39::rand::thread_rng;
17
18use elements_miniscript::{
19    ConfidentialDescriptor, Descriptor, DescriptorPublicKey,
20    bitcoin::{NetworkKind, PrivateKey, PublicKey, bip32::DerivationPath},
21    elements::{
22        EcdsaSighashType,
23        bitcoin::bip32::{Fingerprint, Xpriv, Xpub},
24        sighash::SighashCache,
25    },
26    elementssig_to_rawsig,
27    psbt::PsbtExt,
28    slip77::MasterBlindingKey,
29};
30
31use crate::constants::MIN_FEE;
32use crate::program::ProgramTrait;
33use crate::provider::ProviderTrait;
34use crate::provider::SimplicityNetwork;
35use crate::signer::wtns_injector::WtnsInjector;
36use crate::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature, UTXO};
37
38use super::error::SignerError;
39
40pub const PLACEHOLDER_FEE: u64 = 1;
41
42pub trait SignerTrait {
43    fn sign_program(
44        &self,
45        pst: &PartiallySignedTransaction,
46        program: &dyn ProgramTrait,
47        input_index: usize,
48        network: &SimplicityNetwork,
49    ) -> Result<schnorr::Signature, SignerError>;
50
51    fn sign_input(
52        &self,
53        pst: &PartiallySignedTransaction,
54        input_index: usize,
55    ) -> Result<(PublicKey, ecdsa::Signature), SignerError>;
56}
57
58pub struct Signer {
59    mnemonic: Mnemonic,
60    xprv: Xpriv,
61    provider: Box<dyn ProviderTrait>,
62    network: SimplicityNetwork,
63    secp: Secp256k1<All>,
64}
65
66impl SignerTrait for Signer {
67    fn sign_program(
68        &self,
69        pst: &PartiallySignedTransaction,
70        program: &dyn ProgramTrait,
71        input_index: usize,
72        network: &SimplicityNetwork,
73    ) -> Result<schnorr::Signature, SignerError> {
74        let env = program.get_env(pst, input_index, network)?;
75        let msg = Message::from_digest(env.c_tx_env().sighash_all().to_byte_array());
76
77        let private_key = self.get_private_key();
78        let keypair = Keypair::from_secret_key(&self.secp, &private_key.inner);
79
80        Ok(self.secp.sign_schnorr(&msg, &keypair))
81    }
82
83    fn sign_input(
84        &self,
85        pst: &PartiallySignedTransaction,
86        input_index: usize,
87    ) -> Result<(PublicKey, ecdsa::Signature), SignerError> {
88        let tx = pst.extract_tx()?;
89
90        let mut sighash_cache = SighashCache::new(&tx);
91        let genesis_hash = elements_miniscript::elements::BlockHash::all_zeros();
92
93        let message = pst
94            .sighash_msg(input_index, &mut sighash_cache, None, genesis_hash)?
95            .to_secp_msg();
96
97        let private_key = self.get_private_key();
98        let public_key = private_key.public_key(&self.secp);
99
100        let signature = self.secp.sign_ecdsa_low_r(&message, &private_key.inner);
101
102        Ok((public_key, signature))
103    }
104}
105
106enum Estimate {
107    Success(Transaction, u64),
108    Failure(u64),
109}
110
111impl Signer {
112    pub fn new(mnemonic: &str, provider: Box<dyn ProviderTrait>) -> Self {
113        let secp = Secp256k1::new();
114        let mnemonic: Mnemonic = mnemonic
115            .parse()
116            .map_err(|e: bip39::Error| SignerError::Mnemonic(e.to_string()))
117            .unwrap();
118        let seed = mnemonic.to_seed("");
119        let xprv = Xpriv::new_master(NetworkKind::Test, &seed).unwrap();
120
121        let network = *provider.get_network();
122
123        Self {
124            mnemonic,
125            xprv,
126            provider,
127            network,
128            secp,
129        }
130    }
131
132    // TODO: add an ability to send arbitrary assets
133    pub fn send(&self, to: Script, amount: u64) -> Result<Txid, SignerError> {
134        let mut ft = FinalTransaction::new();
135
136        ft.add_output(PartialOutput::new(to, amount, self.network.policy_asset()));
137
138        let (tx, _fee) = self.finalize(&ft)?;
139
140        Ok(self.provider.broadcast_transaction(&tx)?)
141    }
142
143    pub fn broadcast(&self, tx: &FinalTransaction) -> Result<Txid, SignerError> {
144        let (tx, _fee) = self.finalize(tx)?;
145
146        Ok(self.provider.broadcast_transaction(&tx)?)
147    }
148
149    pub fn finalize(&self, tx: &FinalTransaction) -> Result<(Transaction, u64), SignerError> {
150        let mut signer_utxos = self.get_utxos_asset(self.network.policy_asset())?;
151        let mut set = HashSet::new();
152
153        for input in tx.inputs() {
154            set.insert(OutPoint {
155                txid: input.partial_input.witness_txid,
156                vout: input.partial_input.witness_output_index,
157            });
158        }
159
160        signer_utxos.retain(|utxo| !set.contains(&utxo.outpoint));
161
162        // descending sort of both confidential and explicit utxos
163        signer_utxos.sort_by(|a, b| {
164            let a_value = match a.secrets {
165                Some(secrets) => secrets.value,
166                None => a.explicit_amount(),
167            };
168            let b_value = match b.secrets {
169                Some(secrets) => secrets.value,
170                None => b.explicit_amount(),
171            };
172
173            b_value.cmp(&a_value)
174        });
175
176        let mut fee_tx = tx.clone();
177        let mut curr_fee = MIN_FEE;
178        let fee_rate = self.provider.fetch_fee_rate(1)?;
179
180        for utxo in signer_utxos {
181            let policy_amount_delta = fee_tx.calculate_fee_delta(&self.network);
182
183            if policy_amount_delta >= curr_fee as i64 {
184                match self.estimate_tx(fee_tx.clone(), fee_rate, policy_amount_delta as u64)? {
185                    Estimate::Success(tx, fee) => return Ok((tx, fee)),
186                    Estimate::Failure(required_fee) => curr_fee = required_fee,
187                }
188            }
189
190            fee_tx.add_input(PartialInput::new(utxo), RequiredSignature::NativeEcdsa);
191        }
192
193        // need to try one more time after the loop
194        let policy_amount_delta = fee_tx.calculate_fee_delta(&self.network);
195
196        if policy_amount_delta >= curr_fee as i64 {
197            match self.estimate_tx(fee_tx.clone(), fee_rate, policy_amount_delta as u64)? {
198                Estimate::Success(tx, fee) => return Ok((tx, fee)),
199                Estimate::Failure(required_fee) => curr_fee = required_fee,
200            }
201        }
202
203        Err(SignerError::NotEnoughFunds(curr_fee))
204    }
205
206    pub fn finalize_strict(
207        &self,
208        tx: &FinalTransaction,
209        target_blocks: u32,
210    ) -> Result<(Transaction, u64), SignerError> {
211        let policy_amount_delta = tx.calculate_fee_delta(&self.network);
212
213        if policy_amount_delta < MIN_FEE as i64 {
214            return Err(SignerError::DustAmount(policy_amount_delta));
215        }
216
217        let fee_rate = self.provider.fetch_fee_rate(target_blocks)?;
218
219        // policy_amount_delta will be > 0
220        match self.estimate_tx(tx.clone(), fee_rate, policy_amount_delta as u64)? {
221            Estimate::Success(tx, fee) => Ok((tx, fee)),
222            Estimate::Failure(required_fee) => Err(SignerError::NotEnoughFeeAmount(policy_amount_delta, required_fee)),
223        }
224    }
225
226    pub fn get_provider(&self) -> &dyn ProviderTrait {
227        self.provider.as_ref()
228    }
229
230    pub fn get_confidential_address(&self) -> Address {
231        let mut descriptor =
232            ConfidentialDescriptor::<DescriptorPublicKey>::from_str(&self.get_slip77_descriptor().unwrap())
233                .map_err(|e| SignerError::Slip77Descriptor(e.to_string()))
234                .unwrap();
235
236        // confidential descriptor doesn't support multipath
237        descriptor.descriptor = descriptor.descriptor.into_single_descriptors().unwrap()[0].clone();
238
239        descriptor
240            .at_derivation_index(1)
241            .unwrap()
242            .address(&self.secp, self.network.address_params())
243            .unwrap()
244    }
245
246    pub fn get_address(&self) -> Address {
247        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(&self.get_wpkh_descriptor().unwrap())
248            .map_err(|e| SignerError::WpkhDescriptor(e.to_string()))
249            .unwrap();
250
251        descriptor.into_single_descriptors().unwrap()[0]
252            .at_derivation_index(1)
253            .unwrap()
254            .address(self.network.address_params())
255            .unwrap()
256    }
257
258    pub fn get_utxos(&self) -> Result<Vec<UTXO>, SignerError> {
259        self.get_utxos_filter(&|_| true, &|_| true)
260    }
261
262    pub fn get_utxos_asset(&self, asset: AssetId) -> Result<Vec<UTXO>, SignerError> {
263        self.get_utxos_filter(&|utxo| utxo.explicit_asset() == asset, &|utxo| {
264            utxo.unblinded_asset() == asset
265        })
266    }
267
268    // TODO: can this be optimized to not populate TxOuts that are filtered out?
269    pub fn get_utxos_txid(&self, txid: Txid) -> Result<Vec<UTXO>, SignerError> {
270        self.get_utxos_filter(&|utxo| utxo.outpoint.txid == txid, &|utxo| utxo.outpoint.txid == txid)
271    }
272
273    pub fn get_utxos_filter(
274        &self,
275        explicit_filter: &dyn Fn(&UTXO) -> bool,
276        confidential_filter: &dyn Fn(&UTXO) -> bool,
277    ) -> Result<Vec<UTXO>, SignerError> {
278        // fetch explicit and confidential utxos
279        let mut all_utxos = self.provider.fetch_address_utxos(&self.get_confidential_address())?;
280
281        // filter out only confidential utxos and unblind them
282        let mut confidential_utxos = self.unblind(
283            all_utxos
284                .iter()
285                .filter(|utxo| utxo.txout.value.is_confidential())
286                .cloned()
287                .collect(),
288        )?;
289        // leave only explicit utxos
290        all_utxos.retain(|utxo| !utxo.txout.value.is_confidential());
291
292        all_utxos.retain(explicit_filter);
293        confidential_utxos.retain(confidential_filter);
294
295        // push unblinded utxos to explicit ones
296        all_utxos.extend(confidential_utxos);
297
298        Ok(all_utxos)
299    }
300
301    pub fn get_schnorr_public_key(&self) -> XOnlyPublicKey {
302        let private_key = self.get_private_key();
303        let keypair = Keypair::from_secret_key(&self.secp, &private_key.inner);
304
305        keypair.x_only_public_key().0
306    }
307
308    pub fn get_ecdsa_public_key(&self) -> PublicKey {
309        self.get_private_key().public_key(&self.secp)
310    }
311
312    pub fn get_blinding_public_key(&self) -> PublicKey {
313        self.get_blinding_private_key().public_key(&self.secp)
314    }
315
316    pub fn get_private_key(&self) -> PrivateKey {
317        let master_xprv = self.master_xpriv().unwrap();
318        let full_path = self.get_derivation_path().unwrap();
319
320        let derived = full_path.extend(
321            DerivationPath::from_str("0/1")
322                .map_err(|e| SignerError::DerivationPath(e.to_string()))
323                .unwrap(),
324        );
325
326        let ext_derived = master_xprv.derive_priv(&self.secp, &derived).unwrap();
327
328        PrivateKey::new(ext_derived.private_key, NetworkKind::Test)
329    }
330
331    pub fn get_blinding_private_key(&self) -> PrivateKey {
332        let blinding_key = self
333            .master_slip77()
334            .unwrap()
335            .blinding_private_key(&self.get_address().script_pubkey());
336
337        PrivateKey::new(blinding_key, NetworkKind::Test)
338    }
339
340    fn unblind(&self, utxos: Vec<UTXO>) -> Result<Vec<UTXO>, SignerError> {
341        let mut unblinded: Vec<UTXO> = Vec::new();
342
343        for mut utxo in utxos {
344            let blinding_key = self.get_blinding_private_key();
345            let secrets = utxo.txout.unblind(&self.secp, blinding_key.inner)?;
346
347            utxo.secrets = Some(secrets);
348
349            unblinded.push(utxo);
350        }
351
352        Ok(unblinded)
353    }
354
355    fn estimate_tx(
356        &self,
357        mut fee_tx: FinalTransaction,
358        fee_rate: f32,
359        available_delta: u64,
360    ) -> Result<Estimate, SignerError> {
361        // estimate the tx fee with the change
362        // use this wpkh address as a change script
363        // TODO: this should be confidential
364        fee_tx.add_output(PartialOutput::new(
365            self.get_address().script_pubkey(),
366            PLACEHOLDER_FEE,
367            self.network.policy_asset(),
368        ));
369
370        fee_tx.add_output(PartialOutput::new(
371            Script::new(),
372            PLACEHOLDER_FEE,
373            self.network.policy_asset(),
374        ));
375
376        let final_tx = self.sign_tx(&fee_tx)?;
377        let fee = fee_tx.calculate_fee(final_tx.discount_weight(), fee_rate);
378
379        if available_delta > fee && available_delta - fee >= MIN_FEE {
380            // we have enough funds to cover the change UTXO
381            let outputs = fee_tx.outputs_mut();
382
383            outputs[outputs.len() - 2].amount = available_delta - fee;
384            outputs[outputs.len() - 1].amount = fee;
385
386            let final_tx = self.sign_tx(&fee_tx)?;
387
388            return Ok(Estimate::Success(final_tx, fee));
389        }
390
391        // not enough funds, so we need to estimate without the change
392        fee_tx.remove_output(fee_tx.n_outputs() - 2);
393
394        let final_tx = self.sign_tx(&fee_tx)?;
395        let fee = fee_tx.calculate_fee(final_tx.discount_weight(), fee_rate);
396
397        if available_delta < fee {
398            return Ok(Estimate::Failure(fee));
399        }
400
401        let outputs = fee_tx.outputs_mut();
402
403        // change the fee output amount
404        outputs[outputs.len() - 1].amount = available_delta;
405
406        // finalize the tx with fee and without the change
407        let final_tx = self.sign_tx(&fee_tx)?;
408
409        Ok(Estimate::Success(final_tx, fee))
410    }
411
412    fn sign_tx(&self, tx: &FinalTransaction) -> Result<Transaction, SignerError> {
413        let (mut pst, secrets) = tx.extract_pst();
414        let inputs = tx.inputs();
415
416        if tx.needs_blinding() {
417            pst.blind_last(&mut thread_rng(), &self.secp, &secrets)?;
418        }
419
420        for (index, input_i) in inputs.iter().enumerate() {
421            // we need to prune the program
422            if let Some(program_input) = &input_i.program_input {
423                let signing_info: Option<(&String, &[String])> = match &input_i.required_sig {
424                    RequiredSignature::Witness(wtns_name) => Some((wtns_name, &[])),
425                    RequiredSignature::WitnessWithPath(wtns_name, sig_path) => Some((wtns_name, sig_path)),
426                    _ => None,
427                };
428
429                let signed_witness: Result<WitnessValues, SignerError> = match signing_info {
430                    // sign the program and inject the signature into the witness
431                    Some((witness_name, sig_path)) => Ok(self.get_signed_program_witness(
432                        &pst,
433                        program_input.program.as_ref(),
434                        &program_input.witness.build_witness(),
435                        witness_name,
436                        sig_path,
437                        index,
438                    )?),
439                    // just build the witness
440                    None => Ok(program_input.witness.build_witness()),
441                };
442
443                let pruned_witness =
444                    program_input
445                        .program
446                        .finalize(&pst, &signed_witness.unwrap(), index, &self.network)?;
447
448                pst.inputs_mut()[index].final_script_witness = Some(pruned_witness);
449            } else {
450                // we need to sign the UTXO as is
451                // TODO: do we always sign?
452                let signed_witness = self.sign_input(&pst, index)?;
453                let raw_sig = elementssig_to_rawsig(&(signed_witness.1, EcdsaSighashType::All));
454
455                pst.inputs_mut()[index].final_script_witness = Some(vec![raw_sig, signed_witness.0.to_bytes()]);
456            }
457        }
458
459        Ok(pst.extract_tx()?)
460    }
461
462    fn get_signed_program_witness(
463        &self,
464        pst: &PartiallySignedTransaction,
465        program: &dyn ProgramTrait,
466        witness: &WitnessValues,
467        witness_name: &str,
468        sig_path: &[String],
469        index: usize,
470    ) -> Result<WitnessValues, SignerError> {
471        let signature = self.sign_program(pst, program, index, &self.network)?;
472
473        // inject the signature into the wtns name directly if the path is not provided
474        let sig_val = if !sig_path.is_empty() {
475            let witness_types = program.get_witness_types()?;
476            let witness_type = witness_types
477                .get(&WitnessName::from_str_unchecked(witness_name))
478                .ok_or(SignerError::WtnsFieldNotFound(witness_name.to_string()))?;
479
480            let local_wtns = Arc::new(
481                witness
482                    .get(&WitnessName::from_str_unchecked(witness_name))
483                    .expect("checked above")
484                    .clone(),
485            );
486
487            WtnsInjector::inject_value(
488                &local_wtns,
489                witness_type,
490                sig_path,
491                Value::byte_array(signature.serialize()),
492            )?
493        } else {
494            Value::byte_array(signature.serialize())
495        };
496
497        let mut hm = HashMap::new();
498
499        witness.iter().for_each(|el| {
500            hm.insert(el.0.clone(), el.1.clone());
501        });
502
503        hm.insert(WitnessName::from_str_unchecked(witness_name), sig_val);
504
505        Ok(WitnessValues::from(hm))
506    }
507
508    fn master_slip77(&self) -> Result<MasterBlindingKey, SignerError> {
509        let seed = self.mnemonic.to_seed("");
510
511        Ok(MasterBlindingKey::from_seed(&seed[..]))
512    }
513
514    fn derive_xpriv(&self, path: &DerivationPath) -> Result<Xpriv, SignerError> {
515        Ok(self.xprv.derive_priv(&self.secp, &path)?)
516    }
517
518    fn master_xpriv(&self) -> Result<Xpriv, SignerError> {
519        self.derive_xpriv(&DerivationPath::master())
520    }
521
522    fn derive_xpub(&self, path: &DerivationPath) -> Result<Xpub, SignerError> {
523        let derived = self.derive_xpriv(path)?;
524
525        Ok(Xpub::from_priv(&self.secp, &derived))
526    }
527
528    fn master_xpub(&self) -> Result<Xpub, SignerError> {
529        self.derive_xpub(&DerivationPath::master())
530    }
531
532    fn fingerprint(&self) -> Result<Fingerprint, SignerError> {
533        Ok(self.master_xpub()?.fingerprint())
534    }
535
536    fn get_slip77_descriptor(&self) -> Result<String, SignerError> {
537        let wpkh_descriptor = self.get_wpkh_descriptor()?;
538        let blinding_key = self.master_slip77()?;
539
540        Ok(format!("ct(slip77({blinding_key}),{wpkh_descriptor})"))
541    }
542
543    fn get_wpkh_descriptor(&self) -> Result<String, SignerError> {
544        let fingerprint = self.fingerprint()?;
545        let path = self.get_derivation_path()?;
546        let xpub = self.derive_xpub(&path)?;
547
548        Ok(format!("elwpkh([{fingerprint}/{path}]{xpub}/<0;1>/*)"))
549    }
550
551    fn get_derivation_path(&self) -> Result<DerivationPath, SignerError> {
552        let coin_type = if self.network.is_mainnet() { 1776 } else { 1 };
553        let path = format!("84h/{coin_type}h/0h");
554
555        DerivationPath::from_str(&format!("m/{path}")).map_err(|e| SignerError::DerivationPath(e.to_string()))
556    }
557}
558
559#[cfg(test)]
560mod tests {
561    use crate::provider::EsploraProvider;
562    use crate::utils::random_mnemonic;
563
564    use super::*;
565
566    fn create_signer() -> Signer {
567        let url = "https://blockstream.info/liquidtestnet/api".to_string();
568        let network = SimplicityNetwork::LiquidTestnet;
569
570        Signer::new(random_mnemonic().as_str(), Box::new(EsploraProvider::new(url, network)))
571    }
572
573    #[test]
574    fn keys_correspond_to_address() {
575        let signer = create_signer();
576
577        let address = signer.get_address();
578        let pubkey = signer.get_ecdsa_public_key();
579
580        let derived_addr = Address::p2wpkh(&pubkey, None, signer.get_provider().get_network().address_params());
581
582        assert_eq!(derived_addr.to_string(), address.to_string());
583    }
584
585    #[test]
586    fn descriptors() {
587        let signer = create_signer();
588
589        println!("{}", signer.get_address());
590        println!("{}", signer.get_confidential_address());
591    }
592}