Skip to content

Commit

Permalink
Add ln/dlc tx utilities to dlc crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Tibo-lg committed Feb 8, 2023
1 parent 335a410 commit a495f6b
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 0 deletions.
2 changes: 2 additions & 0 deletions dlc/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use secp256k1_zkp::{
Secp256k1, SecretKey, Signing, Verification,
};

pub mod sub_channel;

/**
* Weight of the buffer transaction:
* INPUT
Expand Down
245 changes: 245 additions & 0 deletions dlc/src/channel/sub_channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
//! Module containing utility functions to create transactions for DLC channels embedded in
//! Lightning channels.
//!
use std::collections::HashMap;

use bitcoin::{
Address, EcdsaSig, OutPoint, PackedLockTime, PublicKey, Script, Sequence, Transaction, TxIn,
TxOut, Witness,
};
use secp256k1_zkp::{PublicKey as SecpPublicKey, Secp256k1, SecretKey, Signing};

use crate::{channel::buffer_descriptor, Error};

use super::{RevokeParams, BUFFER_TX_WEIGHT};

/**
* Weight of the split transaction:
* INPUT
* Overhead -> 10.5 * 4
* Outpoint -> 36 * 4
* scriptSigLength -> 1 * 4
* scriptSig -> 0
* nSequence -> 4 * 4
* Witness item count -> 1
* Witness -> 220
* OUTPUT (x2):
* nValue -> 8 * 4
* scriptPubkeyLen -> 1 * 4
* scriptPubkey -> 34 * 4
* TOTAL: 771
*/
pub const SPLIT_TX_WEIGHT: usize = 771;

/**
* Weight of the ln glue transaction is the same as the buffer transaction.
*/
pub const LN_GLUE_TX_WEIGHT: usize = BUFFER_TX_WEIGHT;

/// Computes the total amount of fee required for a split transaction plus transactions of a DLC
/// channel.
/// # Errors
/// Returns an error if the given `fee_rate_per_vb` is too large resulting in an overflow during
/// the computation.
pub fn dlc_channel_and_split_fee(fee_rate_per_vb: u64) -> Result<u64, Error> {
Ok(crate::util::weight_to_fee(
crate::channel::sub_channel::SPLIT_TX_WEIGHT,
fee_rate_per_vb,
)? + crate::util::weight_to_fee(crate::channel::BUFFER_TX_WEIGHT, fee_rate_per_vb)?
+ crate::util::weight_to_fee(
crate::channel::CET_EXTRA_WEIGHT
+ crate::CET_BASE_WEIGHT
+ crate::P2WPKH_WITNESS_SIZE * 2
+ 18,
fee_rate_per_vb,
)?)
}

#[derive(Clone, Debug)]
/// Structure containing a split transaction and its associated output script.
pub struct SplitTx {
///
pub transaction: Transaction,
///
pub output_script: Script,
}

/// Creates a [`SplitTx`] struct from the given parameter.
/// # Errors
/// Returns an error if the given fee rate makes fee computation overflow, or if the given
/// `channel_value` is not enough for the given `dlc_collateral` and fee.
pub fn create_split_tx(
offer_revoke_params: &RevokeParams,
accept_revoke_params: &RevokeParams,
fund_tx_outpoint: &OutPoint,
channel_value: u64,
dlc_collateral: u64,
fee_rate_per_vb: u64,
) -> Result<SplitTx, Error> {
let output_desc = buffer_descriptor(offer_revoke_params, accept_revoke_params);

let dlc_fee = crate::util::weight_to_fee(super::BUFFER_TX_WEIGHT, fee_rate_per_vb)?
+ crate::util::weight_to_fee(
super::CET_EXTRA_WEIGHT + crate::CET_BASE_WEIGHT + 2 * crate::P2WPKH_WITNESS_SIZE + 18,
fee_rate_per_vb,
)?;

let dlc_output_value = dlc_collateral
.checked_add(dlc_fee)
.ok_or(Error::InvalidArgument)?;

if dlc_output_value
> channel_value
.checked_add(crate::DUST_LIMIT)
.ok_or(Error::InvalidArgument)?
{
return Err(Error::InvalidArgument);
}

let ln_output_value = channel_value
- dlc_output_value
- crate::util::weight_to_fee(SPLIT_TX_WEIGHT, fee_rate_per_vb)?;

let output_values = [ln_output_value, dlc_output_value];

let output = output_values
.iter()
.map(|value| TxOut {
value: *value,
script_pubkey: output_desc.script_pubkey(),
})
.collect::<Vec<_>>();

Ok(SplitTx {
transaction: Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn {
previous_output: *fund_tx_outpoint,
script_sig: Script::default(),
sequence: Sequence::ZERO,
witness: Witness::default(),
}],
output,
},
output_script: output_desc.script_code().unwrap(),
})
}

/// Creates a "glue" transaction for the Lightning side of the split channel.
pub fn create_ln_glue_tx(
split_tx_outpoint: &OutPoint,
ln_fund_script: &Script,
lock_time: PackedLockTime,
nsequence: Sequence,
output_value: u64,
) -> Transaction {
Transaction {
version: 2,
lock_time,
input: {
vec![TxIn {
previous_output: *split_tx_outpoint,
script_sig: Script::default(),
sequence: nsequence,
witness: Witness::default(),
}]
},
output: vec![TxOut {
value: output_value,
script_pubkey: ln_fund_script.to_v0_p2wsh(),
}],
}
}

/// Creates and signs a transaction spending both output of a revoked split transaction that was
/// published on-chain.
pub fn create_and_sign_punish_split_transaction<C: Signing>(
secp: &Secp256k1<C>,
offer_params: &RevokeParams,
accept_params: &RevokeParams,
own_sk: &SecretKey,
counter_publish_sk: &SecretKey,
counter_revoke_sk: &SecretKey,
prev_tx: &Transaction,
dest_address: &Address,
lock_time: u32,
fee_rate_per_vb: u64,
) -> Result<Transaction, Error> {
let descriptor = buffer_descriptor(offer_params, accept_params);

let tx_in = vec![
TxIn {
previous_output: OutPoint {
txid: prev_tx.txid(),
vout: 0,
},
sequence: Sequence::ZERO,
script_sig: Script::default(),
witness: Witness::default(),
},
TxIn {
previous_output: OutPoint {
txid: prev_tx.txid(),
vout: 1,
},
sequence: Sequence::ZERO,
script_sig: Script::default(),
witness: Witness::default(),
},
];

let dest_script_pk_len = dest_address.script_pubkey().len();
let var_int_prefix_len = crate::util::compute_var_int_prefix_size(dest_script_pk_len);
let output_weight = super::N_VALUE_WEIGHT + var_int_prefix_len + dest_script_pk_len * 4;
let tx_fee = crate::util::weight_to_fee(
super::PUNISH_BUFFER_INPUT_WEIGHT * 2 + output_weight,
fee_rate_per_vb,
)?;

let output_value = prev_tx.output[0].value + prev_tx.output[1].value - tx_fee;

let mut tx = Transaction {
version: crate::TX_VERSION,
lock_time: PackedLockTime(lock_time),
input: tx_in,
output: vec![TxOut {
value: output_value,
script_pubkey: dest_address.script_pubkey(),
}],
};

for i in 0..2 {
let mut sigs = HashMap::new();

for sk in &[&own_sk, &counter_publish_sk, &counter_revoke_sk] {
let pk = PublicKey {
inner: SecpPublicKey::from_secret_key(secp, sk),
compressed: true,
};

let pkh = pk.pubkey_hash().as_hash();
sigs.insert(
pkh,
(
pk,
EcdsaSig::sighash_all(crate::util::get_raw_sig_for_tx_input(
secp,
&tx,
i,
&descriptor.script_code()?,
prev_tx.output[i].value,
sk,
)?),
),
);
}

descriptor
.satisfy(&mut tx.input[i], sigs.clone())
.map_err(|_| Error::InvalidArgument)?;
}

Ok(tx)
}

0 comments on commit a495f6b

Please sign in to comment.