dlc/
util.rs

1//! Utility functions not uniquely related to DLC
2
3use bitcoin::sighash::SighashCache;
4use bitcoin::{sighash::EcdsaSighashType, Script, Transaction, TxOut};
5use bitcoin::{Amount, ScriptBuf, Sequence, Witness};
6use bitcoin::{PubkeyHash, WitnessProgram, WitnessVersion};
7use secp256k1_zkp::{ecdsa::Signature, Message, PublicKey, Secp256k1, SecretKey, Signing};
8
9use crate::Error;
10
11// Setting the nSequence for every input of a transaction to this value disables
12// both RBF and nLockTime usage.
13pub(crate) const DISABLE_LOCKTIME: Sequence = Sequence(0xffffffff);
14// Setting the nSequence for every input of a transaction to this value disables
15// RBF but enables nLockTime usage.
16pub(crate) const ENABLE_LOCKTIME: Sequence = Sequence(0xfffffffe);
17
18/// Get a BIP143 (https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki)
19/// signature hash with sighash all flag for a segwit transaction input as
20/// a Message instance
21pub(crate) fn get_sig_hash_msg(
22    tx: &Transaction,
23    input_index: usize,
24    script_pubkey: &Script,
25    value: u64,
26) -> Result<Message, Error> {
27    let sig_hash = SighashCache::new(tx).p2wsh_signature_hash(
28        input_index,
29        script_pubkey,
30        Amount::from_sat(value),
31        EcdsaSighashType::All,
32    )?;
33
34    Ok(Message::from_digest_slice(sig_hash.as_ref()).unwrap())
35}
36
37/// Convert a raw signature to DER encoded and append the sighash type, to use
38/// a signature in a signature script
39pub(crate) fn finalize_sig(sig: &Signature, sig_hash_type: EcdsaSighashType) -> Vec<u8> {
40    [
41        sig.serialize_der().as_ref(),
42        &[sig_hash_type.to_u32() as u8],
43    ]
44    .concat()
45}
46
47/// Generate a signature for a given transaction input using the given secret key.
48pub fn get_raw_sig_for_tx_input<C: Signing>(
49    secp: &Secp256k1<C>,
50    tx: &Transaction,
51    input_index: usize,
52    script_pubkey: &Script,
53    value: u64,
54    sk: &SecretKey,
55) -> Result<Signature, Error> {
56    let sig_hash_msg = get_sig_hash_msg(tx, input_index, script_pubkey, value)?;
57    Ok(secp.sign_ecdsa_low_r(&sig_hash_msg, sk))
58}
59
60/// Returns a DER encoded signature with appended sighash for the specified input
61/// in the provided transaction (assumes a segwit input)
62pub fn get_sig_for_tx_input<C: Signing>(
63    secp: &Secp256k1<C>,
64    tx: &Transaction,
65    input_index: usize,
66    script_pubkey: &Script,
67    value: u64,
68    sig_hash_type: EcdsaSighashType,
69    sk: &SecretKey,
70) -> Result<Vec<u8>, Error> {
71    let sig = get_raw_sig_for_tx_input(secp, tx, input_index, script_pubkey, value, sk)?;
72    Ok(finalize_sig(&sig, sig_hash_type))
73}
74
75/// Returns a DER encoded signature with apended sighash for the specified P2WPKH input.
76pub fn get_sig_for_p2wpkh_input<C: Signing>(
77    secp: &Secp256k1<C>,
78    sk: &SecretKey,
79    tx: &Transaction,
80    input_index: usize,
81    value: u64,
82    sig_hash_type: EcdsaSighashType,
83) -> Result<Vec<u8>, Error> {
84    let script_pubkey = get_pkh_script_pubkey_from_sk(secp, sk);
85    get_sig_for_tx_input(
86        secp,
87        tx,
88        input_index,
89        &script_pubkey,
90        value,
91        sig_hash_type,
92        sk,
93    )
94}
95
96/// Returns the fee for the given weight at given fee rate.
97pub fn weight_to_fee(weight: usize, fee_rate: u64) -> Result<u64, Error> {
98    (f64::ceil((weight as f64) / 4.0) as u64)
99        .checked_mul(fee_rate)
100        .ok_or(Error::InvalidArgument)
101}
102
103/// Return the common base fee for a DLC for the given fee rate.
104pub fn get_common_fee(fee_rate: u64) -> Result<u64, Error> {
105    let base_weight = crate::FUND_TX_BASE_WEIGHT + crate::CET_BASE_WEIGHT;
106    weight_to_fee(base_weight, fee_rate)
107}
108
109fn get_pkh_script_pubkey_from_sk<C: Signing>(secp: &Secp256k1<C>, sk: &SecretKey) -> ScriptBuf {
110    use bitcoin::hashes::*;
111    let pk = bitcoin::PublicKey {
112        compressed: true,
113        inner: PublicKey::from_secret_key(secp, sk),
114    };
115    let mut hash_engine = PubkeyHash::engine();
116
117    pk.write_into(&mut hash_engine)
118        .expect("Error writing hash.");
119    let pk = PubkeyHash::from_engine(hash_engine);
120
121    ScriptBuf::new_p2pkh(&pk)
122}
123
124/// Create a signature for a p2wpkh transaction input using the provided secret key
125/// and places the signature and associated public key on the witness stack.
126pub fn sign_p2wpkh_input<C: Signing>(
127    secp: &Secp256k1<C>,
128    sk: &SecretKey,
129    tx: &mut Transaction,
130    input_index: usize,
131    sig_hash_type: EcdsaSighashType,
132    value: u64,
133) -> Result<(), Error> {
134    tx.input[input_index].witness =
135        get_witness_for_p2wpkh_input(secp, sk, tx, input_index, sig_hash_type, value)?;
136    Ok(())
137}
138
139/// Generates the witness data for a P2WPKH input using the provided secret key.
140pub fn get_witness_for_p2wpkh_input<C: Signing>(
141    secp: &Secp256k1<C>,
142    sk: &SecretKey,
143    tx: &Transaction,
144    input_index: usize,
145    sig_hash_type: EcdsaSighashType,
146    value: u64,
147) -> Result<Witness, Error> {
148    let full_sig = get_sig_for_p2wpkh_input(secp, sk, tx, input_index, value, sig_hash_type)?;
149    Ok(Witness::from_slice(&[
150        full_sig,
151        PublicKey::from_secret_key(secp, sk).serialize().to_vec(),
152    ]))
153}
154
155/// Generates a signature for a given p2wsh transaction input using the given secret
156/// key and info, and places the generated and provided signatures on the input's
157/// witness stack, ordering the signatures based on the ordering of the associated
158/// public keys.
159pub fn sign_multi_sig_input<C: Signing>(
160    secp: &Secp256k1<C>,
161    transaction: &mut Transaction,
162    other_sig: &Signature,
163    other_pk: &PublicKey,
164    sk: &SecretKey,
165    script_pubkey: &Script,
166    input_value: u64,
167    input_index: usize,
168) -> Result<(), Error> {
169    let own_sig = get_sig_for_tx_input(
170        secp,
171        transaction,
172        input_index,
173        script_pubkey,
174        input_value,
175        EcdsaSighashType::All,
176        sk,
177    )?;
178
179    let own_pk = &PublicKey::from_secret_key(secp, sk);
180
181    let other_finalized_sig = finalize_sig(other_sig, EcdsaSighashType::All);
182
183    transaction.input[input_index].witness = if own_pk < other_pk {
184        Witness::from_slice(&[
185            Vec::new(),
186            own_sig,
187            other_finalized_sig,
188            script_pubkey.to_bytes(),
189        ])
190    } else {
191        Witness::from_slice(&[
192            Vec::new(),
193            other_finalized_sig,
194            own_sig,
195            script_pubkey.to_bytes(),
196        ])
197    };
198
199    Ok(())
200}
201
202/// Transforms a redeem script for a p2sh-p2w* output to a script signature.
203pub(crate) fn redeem_script_to_script_sig(redeem: &Script) -> ScriptBuf {
204    match redeem.len() {
205        0 => ScriptBuf::new(),
206        _ => {
207            let bytes = redeem.as_bytes();
208            ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, bytes).unwrap())
209        }
210    }
211}
212
213/// Sorts the given inputs in following the order of the ids.
214pub(crate) fn order_by_serial_ids<T>(inputs: Vec<T>, ids: &[u64]) -> Vec<T> {
215    debug_assert!(inputs.len() == ids.len());
216    let mut combined: Vec<(&u64, T)> = ids.iter().zip(inputs).collect();
217    combined.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap());
218    combined.into_iter().map(|x| x.1).collect()
219}
220
221/// Get the vout and TxOut of the first output with a matching `script_pubkey`
222/// if any.
223pub fn get_output_for_script_pubkey<'a>(
224    tx: &'a Transaction,
225    script_pubkey: &Script,
226) -> Option<(usize, &'a TxOut)> {
227    tx.output
228        .iter()
229        .enumerate()
230        .find(|(_, x)| &x.script_pubkey == script_pubkey)
231}
232
233/// Filters the outputs that have a value lower than the given `dust_limit`.
234pub(crate) fn discard_dust(txs: Vec<TxOut>, dust_limit: u64) -> Vec<TxOut> {
235    txs.into_iter()
236        .filter(|x| x.value.to_sat() >= dust_limit)
237        .collect()
238}
239
240pub(crate) fn get_sequence(lock_time: u32) -> Sequence {
241    if lock_time == 0 {
242        DISABLE_LOCKTIME
243    } else {
244        ENABLE_LOCKTIME
245    }
246}
247
248pub(crate) fn compute_var_int_prefix_size(len: usize) -> usize {
249    bitcoin::VarInt(len as u64).size()
250}
251
252/// Validate that the fee rate is not too high
253pub fn validate_fee_rate(fee_rate_per_vb: u64) -> Result<(), Error> {
254    if fee_rate_per_vb > 25 * 250 {
255        return Err(Error::InvalidArgument);
256    }
257
258    Ok(())
259}