use crate::io_extras::sink;
use crate::prelude::*;
use bitcoin::consensus::Encodable;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::PublicKey;
use bitcoin::{EcdsaSighashType, Script, Transaction, TxOut, VarInt};
use lightning::ln::chan_utils::make_funding_redeemscript;
use log::*;
pub const MAX_VALUE_MSAT: u64 = 21_000_000_0000_0000_000;
pub const MIN_DUST_LIMIT_SATOSHIS: u64 = 330;
pub const MIN_CHAN_DUST_LIMIT_SATOSHIS: u64 = 354;
pub(crate) fn expected_commitment_tx_weight(opt_anchors: bool, num_untrimmed_htlc: usize) -> usize {
const COMMITMENT_TX_BASE_WEIGHT: usize = 724;
const COMMITMENT_TX_BASE_ANCHOR_WEIGHT: usize = 1124;
const COMMITMENT_TX_WEIGHT_PER_HTLC: usize = 172;
let base_weight =
if opt_anchors { COMMITMENT_TX_BASE_ANCHOR_WEIGHT } else { COMMITMENT_TX_BASE_WEIGHT };
base_weight + num_untrimmed_htlc * COMMITMENT_TX_WEIGHT_PER_HTLC
}
pub(crate) fn mutual_close_tx_weight(unsigned_tx: &Transaction) -> usize {
const EXPECTED_MUTUAL_CLOSE_WITNESS_WEIGHT: usize = 2 + 1 + 4 + 72 + 72 + 1 + 1 + 33 + 1 + 33 + 1 + 1; unsigned_tx.weight() + EXPECTED_MUTUAL_CLOSE_WITNESS_WEIGHT
}
pub fn maybe_add_change_output(
tx: &mut Transaction,
input_value: u64,
witness_max_weight: usize,
feerate_sat_per_1000_weight: u32,
change_destination_script: Script,
) -> Result<(), ()> {
if input_value > MAX_VALUE_MSAT / 1000 {
return Err(());
}
let mut output_value = 0;
for output in tx.output.iter() {
output_value += output.value;
if output_value >= input_value {
return Err(());
}
}
let dust_value = change_destination_script.dust_value();
let mut change_output = TxOut { script_pubkey: change_destination_script, value: 0 };
let change_len = change_output.consensus_encode(&mut sink()).map_err(|_| ())?;
let mut weight_with_change: i64 =
tx.weight() as i64 + 2 + witness_max_weight as i64 + change_len as i64 * 4;
weight_with_change += (VarInt(tx.output.len() as u64 + 1).len()
- VarInt(tx.output.len() as u64).len()) as i64
* 4;
let change_value: i64 = (input_value - output_value) as i64
- weight_with_change * feerate_sat_per_1000_weight as i64 / 1000;
if change_value >= dust_value.to_sat() as i64 {
change_output.value = change_value as u64;
tx.output.push(change_output);
} else if (input_value - output_value) as i64
- (tx.weight() as i64 + 2 + witness_max_weight as i64) * feerate_sat_per_1000_weight as i64
/ 1000
< 0
{
return Err(());
}
Ok(())
}
pub(crate) fn estimate_feerate_per_kw(total_fee: u64, weight: u64) -> u32 {
(((total_fee * 1000) + 999) / weight) as u32
}
pub(crate) fn add_holder_sig(
tx: &mut Transaction,
holder_sig: Signature,
counterparty_sig: Signature,
holder_funding_key: &PublicKey,
counterparty_funding_key: &PublicKey,
) {
let funding_redeemscript =
make_funding_redeemscript(&holder_funding_key, &counterparty_funding_key);
tx.input[0].witness.push(Vec::new());
let mut ser_holder_sig = holder_sig.serialize_der().to_vec();
ser_holder_sig.push(EcdsaSighashType::All as u8);
let mut ser_cp_sig = counterparty_sig.serialize_der().to_vec();
ser_cp_sig.push(EcdsaSighashType::All as u8);
let holder_sig_first =
holder_funding_key.serialize()[..] < counterparty_funding_key.serialize()[..];
if holder_sig_first {
tx.input[0].witness.push(ser_holder_sig);
tx.input[0].witness.push(ser_cp_sig);
} else {
tx.input[0].witness.push(ser_cp_sig);
tx.input[0].witness.push(ser_holder_sig);
}
tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
}
pub(crate) fn is_tx_non_malleable(tx: &Transaction, input_txs: &[&Transaction]) -> bool {
let in_tx_map: OrderedMap<_, _> =
input_txs.iter().map(|in_tx| (in_tx.txid(), *in_tx)).collect();
for inp in &tx.input {
match in_tx_map.get(&inp.previous_output.txid) {
None => {
debug!("in_tx_map: {:#?}, tx: {:#?}", &in_tx_map, &tx);
warn!("input tx for {:?} not found", inp.previous_output);
return false; }
Some(in_tx) => {
match in_tx.output.get(*&inp.previous_output.vout as usize) {
None => {
debug!("in_tx_map: {:#?}, tx: {:#?}", &in_tx_map, &tx);
warn!("output for {:?} not found", inp.previous_output);
return false; }
Some(txout) =>
if !txout.script_pubkey.is_witness_program() {
debug!("in_tx_map: {:#?}, tx: {:#?}", &in_tx_map, &tx);
warn!(
"previous_outpoint {:?} is not native-segwit",
inp.previous_output
);
return false;
},
}
}
}
}
true
}
#[cfg(test)]
mod tests {
use bitcoin::hashes::hex::FromHex;
use bitcoin::psbt::serialize::Deserialize;
use bitcoin::Transaction;
use lightning::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
#[test]
fn test_estimate_feerate() {
let weights = vec![
htlc_timeout_tx_weight(false),
htlc_timeout_tx_weight(true),
htlc_success_tx_weight(false),
htlc_success_tx_weight(true),
];
let feerate = 253;
for weight in &weights {
let total_fee = (feerate as u64 * *weight) / 1000;
let estimated_feerate = super::estimate_feerate_per_kw(total_fee, *weight);
assert!(estimated_feerate >= 253);
}
for feerate in (300..5000).step_by(10) {
for weight in &weights {
let total_fee = (feerate as u64 * *weight) / 1000;
let estimated_feerate = super::estimate_feerate_per_kw(total_fee, *weight);
let recovered_total_fee = (estimated_feerate as u64 * *weight) / 1000;
assert_eq!(total_fee, recovered_total_fee);
}
}
}
#[test]
fn test_issue_165() {
let tx = Transaction::deserialize(&Vec::from_hex("0200000001b78e0523c17f8ac709eec54654cc849529c05584bfda6e04c92a3b670476f2a20000000000ffffffff017d4417000000000016001476168b09afc66bd3956efb25cd8b83650bda0c5f00000000").unwrap()).unwrap();
let tx_weight = tx.weight();
let spk = tx.output[0].script_pubkey.len();
let weight = super::mutual_close_tx_weight(&tx);
let fee = 1524999 - tx.output[0].value;
let estimated_feerate = super::estimate_feerate_per_kw(fee, weight as u64);
let expected_tx_weight = (4 + 1 + 36 + 1 + 4 + 1 + 4 )*4 + ((8+1) + spk as u64) * 4; assert_eq!(expected_tx_weight, tx_weight as u64);
assert_eq!(estimated_feerate, 252);
}
}