Skip to content

Low level anchor transaction build support #1055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 153 additions & 9 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use prelude::*;
use core::cmp;
use ln::chan_utils;
use util::transaction_utils::sort_outputs;
use ln::channel::INITIAL_COMMITMENT_NUMBER;
use ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI};
use core::ops::Deref;
use chain;

Expand Down Expand Up @@ -564,6 +564,24 @@ pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, conte
}
}

/// Gets the witnessScript for an anchor output from the funding public key.
/// The witness in the spending input must be:
/// <BIP 143 funding_signature>
/// After 16 blocks of confirmation, an alternative satisfying witness could be:
/// <>
/// (empty vector required to satisfy compliance with MINIMALIF-standard rule)
#[inline]
pub(crate) fn get_anchor_redeemscript(funding_pubkey: &PublicKey) -> Script {
Builder::new().push_slice(&funding_pubkey.serialize()[..])
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_IFDUP)
.push_opcode(opcodes::all::OP_NOTIF)
.push_int(16)
.push_opcode(opcodes::all::OP_CSV)
.push_opcode(opcodes::all::OP_ENDIF)
.into_script()
}

/// Per-channel data used to build transactions in conjunction with the per-commitment data (CommitmentTransaction).
/// The fields are organized by holder/counterparty.
///
Expand Down Expand Up @@ -754,7 +772,7 @@ impl HolderCommitmentTransaction {
funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 })
};
let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable());
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());
HolderCommitmentTransaction {
inner,
counterparty_sig: dummy_sig,
Expand Down Expand Up @@ -841,6 +859,8 @@ pub struct CommitmentTransaction {
to_countersignatory_value_sat: u64,
feerate_per_kw: u32,
htlcs: Vec<HTLCOutputInCommitment>,
// 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()
keys: TxCreationKeys,
// For access to the pre-built transaction, see doc for trust()
Expand All @@ -854,6 +874,7 @@ impl PartialEq for CommitmentTransaction {
self.to_countersignatory_value_sat == o.to_countersignatory_value_sat &&
self.feerate_per_kw == o.feerate_per_kw &&
self.htlcs == o.htlcs &&
self.opt_anchors == o.opt_anchors &&
self.keys == o.keys;
if eq {
debug_assert_eq!(self.built.transaction, o.built.transaction);
Expand All @@ -871,6 +892,7 @@ impl_writeable_tlv_based!(CommitmentTransaction, {
(8, keys, required),
(10, built, required),
(12, htlcs, vec_type),
(14, opt_anchors, option),
});

impl CommitmentTransaction {
Expand All @@ -884,9 +906,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, 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)>, 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).unwrap();
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 (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 @@ -897,6 +919,7 @@ impl CommitmentTransaction {
to_countersignatory_value_sat,
feerate_per_kw,
htlcs,
opt_anchors: if opt_anchors { Some(()) } else { None },
keys,
built: BuiltCommitmentTransaction {
transaction,
Expand All @@ -905,11 +928,11 @@ impl CommitmentTransaction {
}
}

fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters) -> Result<BuiltCommitmentTransaction, ()> {
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<BuiltCommitmentTransaction, ()> {
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)?;
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 transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
let txid = transaction.txid();
Expand All @@ -933,7 +956,7 @@ 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) -> 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)>, channel_parameters: &DirectedChannelTransactionParameters, opt_anchors: bool, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
let contest_delay = channel_parameters.contest_delay();

Expand Down Expand Up @@ -965,6 +988,30 @@ impl CommitmentTransaction {
));
}

if opt_anchors {
if to_broadcaster_value_sat > 0 || !htlcs_with_aux.is_empty() {
let anchor_script = get_anchor_redeemscript(broadcaster_funding_key);
txouts.push((
TxOut {
script_pubkey: anchor_script.to_v0_p2wsh(),
value: ANCHOR_OUTPUT_VALUE_SATOSHI,
},
None,
));
}

if to_countersignatory_value_sat > 0 || !htlcs_with_aux.is_empty() {
let anchor_script = get_anchor_redeemscript(countersignatory_funding_key);
txouts.push((
TxOut {
script_pubkey: anchor_script.to_v0_p2wsh(),
value: ANCHOR_OUTPUT_VALUE_SATOSHI,
},
None,
));
}
}

let mut htlcs = Vec::with_capacity(htlcs_with_aux.len());
for (htlc, _) in htlcs_with_aux {
let script = chan_utils::get_htlc_redeemscript(&htlc, &keys);
Expand Down Expand Up @@ -1081,7 +1128,7 @@ impl CommitmentTransaction {
if keys != self.keys {
return Err(());
}
let tx = self.internal_rebuild_transaction(&keys, channel_parameters)?;
let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey)?;
if self.built.transaction != tx.transaction || self.built.txid != tx.txid {
return Err(());
}
Expand Down Expand Up @@ -1219,8 +1266,105 @@ fn script_for_p2wpkh(key: &PublicKey) -> Script {
#[cfg(test)]
mod tests {
use super::CounterpartyCommitmentSecrets;
use hex;
use ::{hex, chain};
use prelude::*;
use ln::chan_utils::{CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use util::test_utils;
use chain::keysinterface::{KeysInterface, BaseSign};
use bitcoin::Network;
use ln::PaymentHash;

#[test]
fn test_anchors() {
let secp_ctx = Secp256k1::new();

let seed = [42; 32];
let network = Network::Testnet;
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
let signer = keys_provider.get_channel_signer(false, 3000);
let counterparty_signer = keys_provider.get_channel_signer(false, 3000);
let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
let holder_pubkeys = signer.pubkeys();
let counterparty_pubkeys = counterparty_signer.pubkeys();
let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint).unwrap();
let channel_parameters = ChannelTransactionParameters {
holder_pubkeys: holder_pubkeys.clone(),
holder_selected_contest_delay: 0,
is_outbound_from_holder: false,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 })
};

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

// Generate broadcaster and counterparty outputs
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 1000, 2000,
false,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 2);

// Generate broadcaster and counterparty outputs as well as two anchors
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 1000, 2000,
true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 4);

// Generate broadcaster output and anchor
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 3000, 0,
true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 2);

// Generate counterparty output and anchor
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 0, 3000,
true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 2);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a missing test case when we have !htlcs_with_aux.is_empty() ?

diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs
index 20734f95..e226dc84 100644
--- a/lightning/src/ln/chan_utils.rs
+++ b/lightning/src/ln/chan_utils.rs
@@ -1268,7 +1268,8 @@ mod tests {
        use super::CounterpartyCommitmentSecrets;
        use ::{hex, chain};
        use prelude::*;
-       use ln::chan_utils::{CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters};
+       use ln::PaymentHash;
+       use ln::chan_utils::{CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
        use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
        use util::test_utils;
        use chain::keysinterface::{KeysInterface, BaseSign};
@@ -1298,6 +1299,14 @@ mod tests {
                        funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 })
                };
 
+               let payment_hash = PaymentHash([42; 32]);
+               let htlc_info = HTLCOutputInCommitment {
+                       offered: false,
+                       amount_msat: 1,
+                       cltv_expiry: 100,
+                       payment_hash,
+                       transaction_output_index: Some(3),
+               };
                let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
 
                let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
@@ -1337,6 +1346,15 @@ mod tests {
                        &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable()
                );
                assert_eq!(tx.built.transaction.output.len(), 2);
+               let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
+                       0, 3000, 0,
+                       true,
+                       holder_pubkeys.funding_pubkey,
+                       counterparty_pubkeys.funding_pubkey,
+                       keys.clone(), 1,
+                       &mut vec![(htlc_info, ())], &channel_parameters.as_holder_broadcastable()
+               );
+               assert_eq!(tx.built.transaction.output.len(), 4);
        }
 
        #[test]

Copy link
Member Author

@devrandom devrandom Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Slightly modified the test to generate a non-dust HTLC output (in case we start sanity checking that in the future) and pass None for the HTLC tx index (it should be populated by the commitment tx constructor, but we don't check if already set).


// Generate broadcaster output, an HTLC output and two anchors
let payment_hash = PaymentHash([42; 32]);
let htlc_info = HTLCOutputInCommitment {
offered: false,
amount_msat: 1000000,
cltv_expiry: 100,
payment_hash,
transaction_output_index: None,
};

let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
0, 3000, 0,
true,
holder_pubkeys.funding_pubkey,
counterparty_pubkeys.funding_pubkey,
keys.clone(), 1,
&mut vec![(htlc_info, ())], &channel_parameters.as_holder_broadcastable()
);
assert_eq!(tx.built.transaction.output.len(), 4);
}

#[test]
fn test_per_commitment_storage() {
Expand Down
12 changes: 11 additions & 1 deletion lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,9 @@ const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172;
#[cfg(test)]
pub const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172;

/// Maximmum `funding_satoshis` value, according to the BOLT #2 specification
pub const ANCHOR_OUTPUT_VALUE_SATOSHI: u64 = 330;

/// Maximum `funding_satoshis` value, according to the BOLT #2 specification
/// it's 2^24.
pub const MAX_FUNDING_SATOSHIS: u64 = 1 << 24;

Expand Down Expand Up @@ -1206,6 +1208,11 @@ impl<Signer: Sign> Channel<Signer> {

let mut value_to_a = if local { value_to_self } else { value_to_remote };
let mut value_to_b = if local { value_to_remote } else { value_to_self };
let (funding_pubkey_a, funding_pubkey_b) = if local {
(self.get_holder_pubkeys().funding_pubkey, self.get_counterparty_pubkeys().funding_pubkey)
} else {
(self.get_counterparty_pubkeys().funding_pubkey, self.get_holder_pubkeys().funding_pubkey)
};

if value_to_a >= (broadcaster_dust_limit_satoshis as i64) {
log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, value_to_a);
Expand All @@ -1227,6 +1234,9 @@ impl<Signer: Sign> Channel<Signer> {
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
value_to_a as u64,
value_to_b as u64,
false,
funding_pubkey_a,
funding_pubkey_b,
keys.clone(),
feerate_per_kw,
&mut included_non_dust_htlcs,
Expand Down
11 changes: 7 additions & 4 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1297,22 +1297,24 @@ fn test_fee_spike_violation_fails_htlc() {

// Get the EnforcingSigner for each channel, which will be used to (1) get the keys
// needed to sign the new commitment tx and (2) sign the new commitment tx.
let (local_revocation_basepoint, local_htlc_basepoint, local_secret, next_local_point) = {
let (local_revocation_basepoint, local_htlc_basepoint, local_secret, next_local_point, local_funding) = {
let chan_lock = nodes[0].node.channel_state.lock().unwrap();
let local_chan = chan_lock.by_id.get(&chan.2).unwrap();
let chan_signer = local_chan.get_signer();
let pubkeys = chan_signer.pubkeys();
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER),
chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx))
chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx),
chan_signer.pubkeys().funding_pubkey)
};
let (remote_delayed_payment_basepoint, remote_htlc_basepoint,remote_point) = {
let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point, remote_funding) = {
let chan_lock = nodes[1].node.channel_state.lock().unwrap();
let remote_chan = chan_lock.by_id.get(&chan.2).unwrap();
let chan_signer = remote_chan.get_signer();
let pubkeys = chan_signer.pubkeys();
(pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint,
chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx))
chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx),
chan_signer.pubkeys().funding_pubkey)
};

// Assemble the set of keys we can use for signatures for our commitment_signed message.
Expand Down Expand Up @@ -1341,6 +1343,7 @@ fn test_fee_spike_violation_fails_htlc() {
commitment_number,
95000,
local_chan_balance,
false, local_funding, remote_funding,
commit_tx_keys.clone(),
feerate_per_kw,
&mut vec![(accepted_htlc_info, ())],
Expand Down
6 changes: 6 additions & 0 deletions lightning/src/util/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,3 +861,9 @@ impl<A: Writeable, B: Writeable, C: Writeable> Writeable for (A, B, C) {
self.2.write(w)
}
}

impl Readable for () {
fn read<R: Read>(_r: &mut R) -> Result<Self, DecodeError> {
Ok(())
}
}