Skip to content
This repository has been archived by the owner on Oct 3, 2023. It is now read-only.

Commit

Permalink
[WIP] Add custom outputs to commit transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
luckysori authored and bonomat committed Nov 1, 2022
1 parent fde8c3d commit bf3d584
Show file tree
Hide file tree
Showing 5 changed files with 423 additions and 266 deletions.
163 changes: 126 additions & 37 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ pub fn htlc_timeout_tx_weight(opt_anchors: bool) -> u64 {
if opt_anchors { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT }
}

/// Gets the weight for a custom output transaction.
#[inline]
pub fn custom_output_tx_weight() -> u64 {
// TODO: Come up with the real number
600
}

#[derive(PartialEq, Eq)]
pub(crate) enum HTLCClaim {
OfferedTimeout,
Expand Down Expand Up @@ -167,23 +174,23 @@ pub fn build_closing_transaction(to_holder_value_sat: u64, to_counterparty_value
ins
};

let mut txouts: Vec<(TxOut, ())> = Vec::new();
let mut txouts: Vec<(TxOut, (), ())> = Vec::new();

if to_counterparty_value_sat > 0 {
txouts.push((TxOut {
script_pubkey: to_counterparty_script,
value: to_counterparty_value_sat
}, ()));
}, (), ()));
}

if to_holder_value_sat > 0 {
txouts.push((TxOut {
script_pubkey: to_holder_script,
value: to_holder_value_sat
}, ()));
}, (), ()));
}

transaction_utils::sort_outputs(&mut txouts, |_, _| { cmp::Ordering::Equal }); // Ordering doesnt matter if they used our pubkey...
transaction_utils::sort_outputs(&mut txouts, |_, _| { cmp::Ordering::Equal }, |_, _| { cmp::Ordering::Equal }); // Ordering doesnt matter if they used our pubkey...

let mut outputs: Vec<TxOut> = Vec::new();
for out in txouts.drain(..) {
Expand Down Expand Up @@ -420,7 +427,7 @@ pub fn derive_public_revocation_key<T: secp256k1::Verification>(secp_ctx: &Secp2
/// channel basepoints via the new function, or they were obtained via
/// CommitmentTransaction.trust().keys() because we trusted the source of the
/// pre-calculated keys.
#[derive(PartialEq, Eq, Clone)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct TxCreationKeys {
/// The broadcaster's per-commitment public key which was used to derive the other keys.
pub per_commitment_point: PublicKey,
Expand Down Expand Up @@ -555,6 +562,27 @@ impl_writeable_tlv_based!(HTLCOutputInCommitment, {
(8, transaction_output_index, option),
});

/// Information about a custom output as it appears in a commitment transaction.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomOutputInCommitment {
/// The value, in msat, of the custom output. The value as it appears in the commitment transaction is
/// this divided by 1000.
pub amount_msat: u64,
/// The CLTV lock-time at which this custom output expires.
pub cltv_expiry: u32,
/// The position within the commitment transactions' outputs.
///
/// This is set to [`None`] until all the outputs of the commitment transaction are ordered.
/// (I hate this, but we are just following the existing patterns of this library).
pub transaction_output_index: Option<u32>,
}

impl_writeable_tlv_based!(CustomOutputInCommitment, {
(0, amount_msat, required),
(2, cltv_expiry, required),
(4, transaction_output_index, option),
});

#[inline]
pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, opt_anchors: bool, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner();
Expand Down Expand Up @@ -630,13 +658,25 @@ pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommit
}
}

#[inline]
pub(crate) fn get_custom_output_redeemscript_with_explicit_keys(custom_output: &CustomOutputInCommitment, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
// TODO: Build custom output script (or pass it in as an argument!)
Script::new()
}

/// Gets the witness redeemscript for an HTLC output in a commitment transaction. Note that htlc
/// does not need to have its previous_output_index filled.
#[inline]
pub fn get_htlc_redeemscript(htlc: &HTLCOutputInCommitment, opt_anchors: bool, keys: &TxCreationKeys) -> Script {
get_htlc_redeemscript_with_explicit_keys(htlc, opt_anchors, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
}

/// Gets the witness redeemscript for a custom output in a commitment transaction.
#[inline]
pub fn get_custom_output_redeemscript(custom_output: &CustomOutputInCommitment, keys: &TxCreationKeys) -> Script {
get_custom_output_redeemscript_with_explicit_keys(custom_output, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
}

/// Gets the redeemscript for a funding output from the two funding public keys.
/// Note that the order of funding public keys does not matter.
pub fn make_funding_redeemscript(broadcaster: &PublicKey, countersignatory: &PublicKey) -> Script {
Expand Down Expand Up @@ -945,7 +985,8 @@ impl HolderCommitmentTransaction {
opt_anchors: None
};
let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable());
let mut custom_outputs = Vec::new();
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, &mut htlcs_with_aux, &mut custom_outputs, &channel_parameters.as_counterparty_broadcastable());
HolderCommitmentTransaction {
inner,
counterparty_sig: dummy_sig,
Expand Down Expand Up @@ -988,7 +1029,7 @@ impl HolderCommitmentTransaction {
}

/// A pre-built Bitcoin commitment transaction and its txid.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct BuiltCommitmentTransaction {
/// The commitment transaction
pub transaction: Transaction,
Expand Down Expand Up @@ -1151,13 +1192,14 @@ impl<'a> TrustedClosingTransaction<'a> {
///
/// This class can be used inside a signer implementation to generate a signature given the relevant
/// secret key.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct CommitmentTransaction {
commitment_number: u64,
to_broadcaster_value_sat: u64,
to_countersignatory_value_sat: u64,
feerate_per_kw: u32,
htlcs: Vec<HTLCOutputInCommitment>,
custom_outputs: Vec<CustomOutputInCommitment>,
// A boolean that is serialization backwards-compatible
opt_anchors: Option<()>,
// A cache of the parties' pubkeys required to construct the transaction, see doc for trust()
Expand Down Expand Up @@ -1193,6 +1235,7 @@ impl_writeable_tlv_based!(CommitmentTransaction, {
(10, built, required),
(12, htlcs, vec_type),
(14, opt_anchors, option),
(16, custom_outputs, vec_type),
});

impl CommitmentTransaction {
Expand All @@ -1206,9 +1249,9 @@ impl CommitmentTransaction {
/// Only include HTLCs that are above the dust limit for the channel.
///
/// (C-not exported) due to the generic though we likely should expose a version without
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, opt_anchors: bool, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, opt_anchors: bool, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, custom_outputs: &mut Vec<CustomOutputInCommitment>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
// Sort outputs and populate output indices while keeping track of the auxiliary data
let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, opt_anchors, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
let (outputs, htlcs, custom_outputs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, custom_outputs, channel_parameters, opt_anchors, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();

let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
Expand All @@ -1219,9 +1262,10 @@ impl CommitmentTransaction {
to_countersignatory_value_sat,
feerate_per_kw,
htlcs,
custom_outputs,
opt_anchors: if opt_anchors { Some(()) } else { None },
keys,
built: BuiltCommitmentTransaction {
built: BuiltCommitmentTransaction {
transaction,
txid
},
Expand All @@ -1232,7 +1276,8 @@ impl CommitmentTransaction {
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);

let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, self.opt_anchors.is_some(), broadcaster_funding_key, countersignatory_funding_key)?;
let mut custom_outputs = self.custom_outputs.clone();
let (outputs, _, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, &mut custom_outputs, channel_parameters, self.opt_anchors.is_some(), broadcaster_funding_key, countersignatory_funding_key)?;

let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
let txid = transaction.txid();
Expand All @@ -1256,11 +1301,11 @@ impl CommitmentTransaction {
// - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
// caller needs to have sorted together with the HTLCs so it can keep track of the output index
// - building of a bitcoin transaction during a verify() call, in which case T is just ()
fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, opt_anchors: bool, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, custom_outputs_in_commitment: &mut Vec<CustomOutputInCommitment>, channel_parameters: &DirectedChannelTransactionParameters, opt_anchors: bool, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>, Vec<CustomOutputInCommitment>), ()> {
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
let contest_delay = channel_parameters.contest_delay();

let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new();
let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>, Option<&mut CustomOutputInCommitment>)> = Vec::new();

if to_countersignatory_value_sat > 0 {
let script = if opt_anchors {
Expand All @@ -1274,6 +1319,7 @@ impl CommitmentTransaction {
value: to_countersignatory_value_sat,
},
None,
None,
))
}

Expand All @@ -1289,6 +1335,7 @@ impl CommitmentTransaction {
value: to_broadcaster_value_sat,
},
None,
None,
));
}

Expand All @@ -1301,6 +1348,7 @@ impl CommitmentTransaction {
value: ANCHOR_OUTPUT_VALUE_SATOSHI,
},
None,
None,
));
}

Expand All @@ -1312,6 +1360,7 @@ impl CommitmentTransaction {
value: ANCHOR_OUTPUT_VALUE_SATOSHI,
},
None,
None,
));
}
}
Expand All @@ -1323,34 +1372,63 @@ impl CommitmentTransaction {
script_pubkey: script.to_v0_p2wsh(),
value: htlc.amount_msat / 1000,
};
txouts.push((txout, Some(htlc)));
txouts.push((txout, Some(htlc), None));
}

let mut custom_outputs = Vec::with_capacity(custom_outputs_in_commitment.len());
for custom_output_in_commitment in custom_outputs_in_commitment {
let script = chan_utils::get_custom_output_redeemscript(&custom_output_in_commitment, &keys);
let txout = TxOut {
script_pubkey: script.to_v0_p2wsh(),
value: custom_output_in_commitment.amount_msat / 1000,
};
txouts.push((txout, None, Some(custom_output_in_commitment)));
}

// Sort output in BIP-69 order (amount, scriptPubkey). Tie-breaks based on HTLC
// CLTV expiration height.
sort_outputs(&mut txouts, |a, b| {
if let &Some(ref a_htlcout) = a {
if let &Some(ref b_htlcout) = b {
a_htlcout.cltv_expiry.cmp(&b_htlcout.cltv_expiry)
// Note that due to hash collisions, we have to have a fallback comparison
// here for fuzzing mode (otherwise at least chanmon_fail_consistency
// may fail)!
.then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0))
// For non-HTLC outputs, if they're copying our SPK we don't really care if we
// close the channel due to mismatches - they're doing something dumb:
} else { cmp::Ordering::Equal }
} else { cmp::Ordering::Equal }
});
sort_outputs(&mut txouts,
|a: &Option<&mut HTLCOutputInCommitment>, b: &Option<&mut HTLCOutputInCommitment>| {
if let &Some(ref a_htlcout) = a {
if let &Some(ref b_htlcout) = b {
a_htlcout.cltv_expiry.cmp(&b_htlcout.cltv_expiry)
// Note that due to hash collisions, we have to have a fallback comparison
// here for fuzzing mode (otherwise at least chanmon_fail_consistency
// may fail)!
.then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0))
// For non-HTLC outputs, if they're copying our SPK we don't really care if we
// close the channel due to mismatches - they're doing something dumb:
} else { cmp::Ordering::Equal }
} else { cmp::Ordering::Equal }
},
|a: &Option<&mut CustomOutputInCommitment>, b: &Option<&mut CustomOutputInCommitment>| {
if let &Some(ref a_custom_output) = a {
if let &Some(ref b_custom_output) = b {
a_custom_output.cltv_expiry.cmp(&b_custom_output.cltv_expiry)
} else { cmp::Ordering::Equal }
} else { cmp::Ordering::Equal }
}
);

let mut outputs = Vec::with_capacity(txouts.len());
for (idx, out) in txouts.drain(..).enumerate() {
if let Some(htlc) = out.1 {
htlc.transaction_output_index = Some(idx as u32);
htlcs.push(htlc.clone());
}
match (out.1, out.2) {
(None, None) => {},
(Some(htlc), None) => {
htlc.transaction_output_index = Some(idx as u32);
htlcs.push(htlc.clone());
},
(None, Some(custom_output)) => {
custom_output.transaction_output_index = Some(idx as u32);
custom_outputs.push(custom_output.clone());
},
(Some(_), Some(_)) => unreachable!("Output cannot be both HTLC and custom!"),
}

outputs.push(out.0);
}
Ok((outputs, htlcs))

Ok((outputs, htlcs, custom_outputs))
}

fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec<TxIn>) {
Expand Down Expand Up @@ -1618,6 +1696,7 @@ mod tests {
};

let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
let mut custom_outputs: Vec<_> = Vec::new();

// Generate broadcaster and counterparty outputs
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
Expand All @@ -1626,7 +1705,9 @@ mod tests {
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
&mut htlcs_with_aux,
&mut custom_outputs,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 2);
assert_eq!(tx.built.transaction.output[1].script_pubkey, get_p2wpkh_redeemscript(&counterparty_pubkeys.payment_point));
Expand All @@ -1638,7 +1719,9 @@ mod tests {
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
&mut htlcs_with_aux,
&mut custom_outputs,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 4);
assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&counterparty_pubkeys.payment_point).to_v0_p2wsh());
Expand All @@ -1650,7 +1733,9 @@ mod tests {
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
&mut htlcs_with_aux,
&mut custom_outputs,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 2);

Expand All @@ -1661,7 +1746,9 @@ mod tests {
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
&mut htlcs_with_aux,
&mut custom_outputs,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 2);

Expand Down Expand Up @@ -1689,6 +1776,7 @@ mod tests {
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
&mut custom_outputs,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 3);
Expand All @@ -1708,6 +1796,7 @@ mod tests {
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
&mut custom_outputs,
&channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 5);
Expand Down
Loading

0 comments on commit bf3d584

Please sign in to comment.