Skip to main content

smplx_sdk/signer/
core.rs

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