// licenses.
//! Various utilities for building scripts and deriving keys related to channels. These are
-//! largely of interest for those implementing the traits on [`chain::keysinterface`] by hand.
+//! largely of interest for those implementing the traits on [`crate::sign`] by hand.
use bitcoin::blockdata::script::{Script,Builder};
use bitcoin::blockdata::opcodes;
use bitcoin::hashes::ripemd160::Hash as Ripemd160;
use bitcoin::hash_types::{Txid, PubkeyHash};
+use crate::chain::chaininterface::fee_for_weight;
+use crate::chain::package::WEIGHT_REVOKED_OUTPUT;
+use crate::sign::EntropySource;
use crate::ln::{PaymentHash, PaymentPreimage};
use crate::ln::msgs::DecodeError;
-use crate::util::ser::{Readable, Writeable, Writer};
+use crate::util::ser::{Readable, RequiredWrapper, Writeable, Writer};
use crate::util::transaction_utils;
use bitcoin::secp256k1::{SecretKey, PublicKey, Scalar};
use crate::ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI};
use core::ops::Deref;
use crate::chain;
-use crate::util::crypto::sign;
+use crate::ln::features::ChannelTypeFeatures;
+use crate::util::crypto::{sign, sign_with_aux_rand};
/// Maximum number of one-way in-flight HTLC (protocol-level value).
pub const MAX_HTLCS: u16 = 483;
/// This is the maximum post-anchor value.
pub const MAX_ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 143;
+/// The upper bound weight of an anchor input.
+pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 116;
+/// The upper bound weight of an HTLC timeout input from a commitment transaction with anchor
+/// outputs.
+pub const HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 288;
+/// The upper bound weight of an HTLC success input from a commitment transaction with anchor
+/// outputs.
+pub const HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 327;
+
/// Gets the weight for an HTLC-Success transaction.
#[inline]
-pub fn htlc_success_tx_weight(opt_anchors: bool) -> u64 {
+pub fn htlc_success_tx_weight(channel_type_features: &ChannelTypeFeatures) -> u64 {
const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
const HTLC_SUCCESS_ANCHOR_TX_WEIGHT: u64 = 706;
- if opt_anchors { HTLC_SUCCESS_ANCHOR_TX_WEIGHT } else { HTLC_SUCCESS_TX_WEIGHT }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { HTLC_SUCCESS_ANCHOR_TX_WEIGHT } else { HTLC_SUCCESS_TX_WEIGHT }
}
/// Gets the weight for an HTLC-Timeout transaction.
#[inline]
-pub fn htlc_timeout_tx_weight(opt_anchors: bool) -> u64 {
+pub fn htlc_timeout_tx_weight(channel_type_features: &ChannelTypeFeatures) -> u64 {
const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
const HTLC_TIMEOUT_ANCHOR_TX_WEIGHT: u64 = 666;
- if opt_anchors { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT }
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() { HTLC_TIMEOUT_ANCHOR_TX_WEIGHT } else { HTLC_TIMEOUT_TX_WEIGHT }
}
/// Describes the type of HTLC claim as determined by analyzing the witness.
/// 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,
});
#[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 {
+pub(crate) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_htlc_key: &PublicKey, countersignatory_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner();
if htlc.offered {
let mut bldr = Builder::new().push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_ENDIF);
- if opt_anchors {
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
bldr = bldr.push_opcode(opcodes::all::OP_PUSHNUM_1)
.push_opcode(opcodes::all::OP_CSV)
.push_opcode(opcodes::all::OP_DROP);
.push_opcode(opcodes::all::OP_DROP)
.push_opcode(opcodes::all::OP_CHECKSIG)
.push_opcode(opcodes::all::OP_ENDIF);
- if opt_anchors {
+ if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
bldr = bldr.push_opcode(opcodes::all::OP_PUSHNUM_1)
.push_opcode(opcodes::all::OP_CSV)
.push_opcode(opcodes::all::OP_DROP);
/// 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)
+pub fn get_htlc_redeemscript(htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, keys: &TxCreationKeys) -> Script {
+ get_htlc_redeemscript_with_explicit_keys(htlc, channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key)
}
/// Gets the redeemscript for a funding output from the two funding public keys.
///
/// Panics if htlc.transaction_output_index.is_none() (as such HTLCs do not appear in the
/// commitment transaction).
-pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool, use_non_zero_fee_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction {
+pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction {
let mut txins: Vec<TxIn> = Vec::new();
- txins.push(build_htlc_input(commitment_txid, htlc, opt_anchors));
+ txins.push(build_htlc_input(commitment_txid, htlc, channel_type_features));
let mut txouts: Vec<TxOut> = Vec::new();
txouts.push(build_htlc_output(
- feerate_per_kw, contest_delay, htlc, opt_anchors, use_non_zero_fee_anchors,
+ feerate_per_kw, contest_delay, htlc, channel_type_features,
broadcaster_delayed_payment_key, revocation_key
));
}
}
-pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommitment, opt_anchors: bool) -> TxIn {
+pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures) -> TxIn {
TxIn {
previous_output: OutPoint {
txid: commitment_txid.clone(),
vout: htlc.transaction_output_index.expect("Can't build an HTLC transaction for a dust output"),
},
script_sig: Script::new(),
- sequence: Sequence(if opt_anchors { 1 } else { 0 }),
+ sequence: Sequence(if channel_type_features.supports_anchors_zero_fee_htlc_tx() { 1 } else { 0 }),
witness: Witness::new(),
}
}
pub(crate) fn build_htlc_output(
- feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool,
- use_non_zero_fee_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey
+ feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey
) -> TxOut {
let weight = if htlc.offered {
- htlc_timeout_tx_weight(opt_anchors)
+ htlc_timeout_tx_weight(channel_type_features)
} else {
- htlc_success_tx_weight(opt_anchors)
+ htlc_success_tx_weight(channel_type_features)
};
- let output_value = if opt_anchors && !use_non_zero_fee_anchors {
+ let output_value = if channel_type_features.supports_anchors_zero_fee_htlc_tx() && !channel_type_features.supports_anchors_nonzero_fee_htlc_tx() {
htlc.amount_msat / 1000
} else {
let total_fee = feerate_per_kw as u64 * weight / 1000;
/// Returns the witness required to satisfy and spend a HTLC input.
pub fn build_htlc_input_witness(
local_sig: &Signature, remote_sig: &Signature, preimage: &Option<PaymentPreimage>,
- redeem_script: &Script, opt_anchors: bool,
+ redeem_script: &Script, channel_type_features: &ChannelTypeFeatures,
) -> Witness {
- let remote_sighash_type = if opt_anchors {
+ let remote_sighash_type = if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
witness
}
+/// Pre-anchors channel type features did not use to get serialized in the following six structs:
+/// — [`ChannelTransactionParameters`]
+/// — [`CommitmentTransaction`]
+/// — [`CounterpartyOfferedHTLCOutput`]
+/// — [`CounterpartyReceivedHTLCOutput`]
+/// — [`HolderHTLCOutput`]
+/// — [`HolderFundingOutput`]
+///
+/// To ensure a forwards-compatible serialization, we use odd TLV fields. However, if new features
+/// are used that could break security, where old signers should be prevented from handling the
+/// serialized data, an optional even-field TLV will be used as a stand-in to break compatibility.
+///
+/// This method determines whether or not that option needs to be set based on the chanenl type
+/// features, and returns it.
+///
+/// [`CounterpartyOfferedHTLCOutput`]: crate::chain::package::CounterpartyOfferedHTLCOutput
+/// [`CounterpartyReceivedHTLCOutput`]: crate::chain::package::CounterpartyReceivedHTLCOutput
+/// [`HolderHTLCOutput`]: crate::chain::package::HolderHTLCOutput
+/// [`HolderFundingOutput`]: crate::chain::package::HolderFundingOutput
+pub(crate) fn legacy_deserialization_prevention_marker_for_channel_type_features(features: &ChannelTypeFeatures) -> Option<()> {
+ let mut legacy_version_bit_set = ChannelTypeFeatures::only_static_remote_key();
+ legacy_version_bit_set.set_scid_privacy_required();
+ legacy_version_bit_set.set_zero_conf_required();
+
+ if features.is_subset(&legacy_version_bit_set) {
+ None
+ } else {
+ Some(())
+ }
+}
+
/// Gets the witnessScript for the to_remote output when anchors are enabled.
#[inline]
pub fn get_to_countersignatory_with_anchors_redeemscript(payment_point: &PublicKey) -> Script {
.into_script()
}
-#[cfg(anchors)]
/// Locates the output with an anchor script paying to `funding_pubkey` within `commitment_tx`.
pub(crate) fn get_anchor_output<'a>(commitment_tx: &'a Transaction, funding_pubkey: &PublicKey) -> Option<(u32, &'a TxOut)> {
let anchor_script = chan_utils::get_anchor_redeemscript(funding_pubkey).to_v0_p2wsh();
pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
/// The late-bound funding outpoint
pub funding_outpoint: Option<chain::transaction::OutPoint>,
- /// Are anchors (zero fee HTLC transaction variant) used for this channel. Boolean is
- /// serialization backwards-compatible.
- pub opt_anchors: Option<()>,
- /// Are non-zero-fee anchors are enabled (used in conjuction with opt_anchors)
- /// It is intended merely for backwards compatibility with signers that need it.
- /// There is no support for this feature in LDK channel negotiation.
- pub opt_non_zero_fee_anchors: Option<()>,
+ /// This channel's type, as negotiated during channel open. For old objects where this field
+ /// wasn't serialized, it will default to static_remote_key at deserialization.
+ pub channel_type_features: ChannelTypeFeatures
}
/// Late-bound per-channel counterparty data used to build transactions.
(2, selected_contest_delay, required),
});
-impl_writeable_tlv_based!(ChannelTransactionParameters, {
- (0, holder_pubkeys, required),
- (2, holder_selected_contest_delay, required),
- (4, is_outbound_from_holder, required),
- (6, counterparty_parameters, option),
- (8, funding_outpoint, option),
- (10, opt_anchors, option),
- (12, opt_non_zero_fee_anchors, option),
-});
+impl Writeable for ChannelTransactionParameters {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.holder_pubkeys, required),
+ (2, self.holder_selected_contest_delay, required),
+ (4, self.is_outbound_from_holder, required),
+ (6, self.counterparty_parameters, option),
+ (8, self.funding_outpoint, option),
+ (10, legacy_deserialization_prevention_marker, option),
+ (11, self.channel_type_features, required),
+ });
+ Ok(())
+ }
+}
+
+impl Readable for ChannelTransactionParameters {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ let mut holder_pubkeys = RequiredWrapper(None);
+ let mut holder_selected_contest_delay = RequiredWrapper(None);
+ let mut is_outbound_from_holder = RequiredWrapper(None);
+ let mut counterparty_parameters = None;
+ let mut funding_outpoint = None;
+ let mut _legacy_deserialization_prevention_marker: Option<()> = None;
+ let mut channel_type_features = None;
+
+ read_tlv_fields!(reader, {
+ (0, holder_pubkeys, required),
+ (2, holder_selected_contest_delay, required),
+ (4, is_outbound_from_holder, required),
+ (6, counterparty_parameters, option),
+ (8, funding_outpoint, option),
+ (10, _legacy_deserialization_prevention_marker, option),
+ (11, channel_type_features, option),
+ });
+
+ let mut additional_features = ChannelTypeFeatures::empty();
+ additional_features.set_anchors_nonzero_fee_htlc_tx_required();
+ chain::package::verify_channel_type_features(&channel_type_features, Some(&additional_features))?;
+
+ Ok(Self {
+ holder_pubkeys: holder_pubkeys.0.unwrap(),
+ holder_selected_contest_delay: holder_selected_contest_delay.0.unwrap(),
+ is_outbound_from_holder: is_outbound_from_holder.0.unwrap(),
+ counterparty_parameters,
+ funding_outpoint,
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
/// Static channel fields used to build transactions given per-commitment fields, organized by
/// broadcaster/countersignatory.
impl<'a> DirectedChannelTransactionParameters<'a> {
/// Get the channel pubkeys for the broadcaster
- pub fn broadcaster_pubkeys(&self) -> &ChannelPublicKeys {
+ pub fn broadcaster_pubkeys(&self) -> &'a ChannelPublicKeys {
if self.holder_is_broadcaster {
&self.inner.holder_pubkeys
} else {
}
/// Get the channel pubkeys for the countersignatory
- pub fn countersignatory_pubkeys(&self) -> &ChannelPublicKeys {
+ pub fn countersignatory_pubkeys(&self) -> &'a ChannelPublicKeys {
if self.holder_is_broadcaster {
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
} else {
}
/// Whether to use anchors for this channel
- pub fn opt_anchors(&self) -> bool {
- self.inner.opt_anchors.is_some()
+ pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures {
+ &self.inner.channel_type_features
}
}
/// Information needed to build and sign a holder's commitment transaction.
///
/// The transaction is only signed once we are ready to broadcast.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct HolderCommitmentTransaction {
inner: CommitmentTransaction,
/// Our counterparty's signature for the transaction
(0, inner, required),
(2, counterparty_sig, required),
(4, holder_sig_first, required),
- (6, counterparty_htlc_sigs, vec_type),
+ (6, counterparty_htlc_sigs, required_vec),
});
impl HolderCommitmentTransaction {
#[cfg(test)]
- pub fn dummy() -> Self {
+ pub fn dummy(htlcs: &mut Vec<(HTLCOutputInCommitment, ())>) -> Self {
let secp_ctx = Secp256k1::new();
let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let dummy_sig = sign(&secp_ctx, &secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
is_outbound_from_holder: false,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }),
funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
- opt_anchors: None,
- opt_non_zero_fee_anchors: None,
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
};
- 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 counterparty_htlc_sigs = Vec::new();
+ for _ in 0..htlcs.len() {
+ counterparty_htlc_sigs.push(dummy_sig);
+ }
+ let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
+ htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
HolderCommitmentTransaction {
inner,
counterparty_sig: dummy_sig,
- counterparty_htlc_sigs: Vec::new(),
+ counterparty_htlc_sigs,
holder_sig_first: false
}
}
}
/// A pre-built Bitcoin commitment transaction and its txid.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct BuiltCommitmentTransaction {
/// The commitment transaction
pub transaction: Transaction,
hash_to_message!(sighash)
}
- /// Sign a transaction, either because we are counter-signing the counterparty's transaction or
- /// because we are about to broadcast a holder transaction.
- pub fn sign<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
+ /// Signs the counterparty's commitment transaction.
+ pub fn sign_counterparty_commitment<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
let sighash = self.get_sighash_all(funding_redeemscript, channel_value_satoshis);
sign(secp_ctx, &sighash, funding_key)
}
+
+ /// Signs the holder commitment transaction because we are about to broadcast it.
+ pub fn sign_holder_commitment<T: secp256k1::Signing, ES: Deref>(
+ &self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64,
+ entropy_source: &ES, secp_ctx: &Secp256k1<T>
+ ) -> Signature where ES::Target: EntropySource {
+ let sighash = self.get_sighash_all(funding_redeemscript, channel_value_satoshis);
+ sign_with_aux_rand(secp_ctx, &sighash, funding_key, entropy_source)
+ }
}
/// This class tracks the per-transaction information needed to build a closing transaction and will
impl<'a> TrustedClosingTransaction<'a> {
/// The pre-built Bitcoin commitment transaction
- pub fn built_transaction(&self) -> &Transaction {
+ pub fn built_transaction(&self) -> &'a Transaction {
&self.inner.built
}
///
/// 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,
+ to_broadcaster_delay: Option<u16>, // Added in 0.0.117
feerate_per_kw: u32,
htlcs: Vec<HTLCOutputInCommitment>,
- // A boolean that is serialization backwards-compatible
- opt_anchors: Option<()>,
- // Whether non-zero-fee anchors should be used
- opt_non_zero_fee_anchors: Option<()>,
+ // Note that on upgrades, some features of existing outputs may be missed.
+ channel_type_features: ChannelTypeFeatures,
// 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()
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.channel_type_features == o.channel_type_features &&
self.keys == o.keys;
if eq {
debug_assert_eq!(self.built.transaction, o.built.transaction);
}
}
-impl_writeable_tlv_based!(CommitmentTransaction, {
- (0, commitment_number, required),
- (2, to_broadcaster_value_sat, required),
- (4, to_countersignatory_value_sat, required),
- (6, feerate_per_kw, required),
- (8, keys, required),
- (10, built, required),
- (12, htlcs, vec_type),
- (14, opt_anchors, option),
- (16, opt_non_zero_fee_anchors, option),
-});
+impl Writeable for CommitmentTransaction {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let legacy_deserialization_prevention_marker = legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features);
+ write_tlv_fields!(writer, {
+ (0, self.commitment_number, required),
+ (1, self.to_broadcaster_delay, option),
+ (2, self.to_broadcaster_value_sat, required),
+ (4, self.to_countersignatory_value_sat, required),
+ (6, self.feerate_per_kw, required),
+ (8, self.keys, required),
+ (10, self.built, required),
+ (12, self.htlcs, required_vec),
+ (14, legacy_deserialization_prevention_marker, option),
+ (15, self.channel_type_features, required),
+ });
+ Ok(())
+ }
+}
+
+impl Readable for CommitmentTransaction {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
+ (0, commitment_number, required),
+ (1, to_broadcaster_delay, option),
+ (2, to_broadcaster_value_sat, required),
+ (4, to_countersignatory_value_sat, required),
+ (6, feerate_per_kw, required),
+ (8, keys, required),
+ (10, built, required),
+ (12, htlcs, required_vec),
+ (14, _legacy_deserialization_prevention_marker, option),
+ (15, channel_type_features, option),
+ });
+
+ let mut additional_features = ChannelTypeFeatures::empty();
+ additional_features.set_anchors_nonzero_fee_htlc_tx_required();
+ chain::package::verify_channel_type_features(&channel_type_features, Some(&additional_features))?;
+
+ Ok(Self {
+ commitment_number: commitment_number.0.unwrap(),
+ to_broadcaster_value_sat: to_broadcaster_value_sat.0.unwrap(),
+ to_countersignatory_value_sat: to_countersignatory_value_sat.0.unwrap(),
+ to_broadcaster_delay,
+ feerate_per_kw: feerate_per_kw.0.unwrap(),
+ keys: keys.0.unwrap(),
+ built: built.0.unwrap(),
+ htlcs,
+ channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
+ })
+ }
+}
impl CommitmentTransaction {
/// Construct an object of the class while assigning transaction output indices to HTLCs.
///
/// 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 {
+ /// This is not exported to bindings users 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, 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, opt_anchors, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
+ let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &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);
commitment_number,
to_broadcaster_value_sat,
to_countersignatory_value_sat,
+ to_broadcaster_delay: Some(channel_parameters.contest_delay()),
feerate_per_kw,
htlcs,
- opt_anchors: if opt_anchors { Some(()) } else { None },
+ channel_type_features: channel_parameters.channel_type_features().clone(),
keys,
built: BuiltCommitmentTransaction {
transaction,
txid
},
- opt_non_zero_fee_anchors: None,
}
}
/// Use non-zero fee anchors
///
- /// (C-not exported) due to move, and also not likely to be useful for binding users
+ /// This is not exported to bindings users due to move, and also not likely to be useful for binding users
pub fn with_non_zero_fee_anchors(mut self) -> Self {
- self.opt_non_zero_fee_anchors = Some(());
+ self.channel_type_features.set_anchors_nonzero_fee_htlc_tx_required();
self
}
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 (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
let txid = transaction.txid();
// - 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)>, channel_parameters: &DirectedChannelTransactionParameters, 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();
let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new();
if to_countersignatory_value_sat > 0 {
- let script = if opt_anchors {
+ let script = if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
get_to_countersignatory_with_anchors_redeemscript(&countersignatory_pubkeys.payment_point).to_v0_p2wsh()
} else {
Payload::p2wpkh(&BitcoinPublicKey::new(countersignatory_pubkeys.payment_point)).unwrap().script_pubkey()
));
}
- if opt_anchors {
+ if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
if to_broadcaster_value_sat > 0 || !htlcs_with_aux.is_empty() {
let anchor_script = get_anchor_redeemscript(broadcaster_funding_key);
txouts.push((
let mut htlcs = Vec::with_capacity(htlcs_with_aux.len());
for (htlc, _) in htlcs_with_aux {
- let script = chan_utils::get_htlc_redeemscript(&htlc, opt_anchors, &keys);
+ let script = chan_utils::get_htlc_redeemscript(&htlc, &channel_parameters.channel_type_features(), &keys);
let txout = TxOut {
script_pubkey: script.to_v0_p2wsh(),
value: htlc.amount_msat / 1000,
/// which were included in this commitment transaction in output order.
/// The transaction index is always populated.
///
- /// (C-not exported) as we cannot currently convert Vec references to/from C, though we should
+ /// This is not exported to bindings users as we cannot currently convert Vec references to/from C, though we should
/// expose a less effecient version which creates a Vec of references in the future.
pub fn htlcs(&self) -> &Vec<HTLCOutputInCommitment> {
&self.htlcs
}
/// The pre-built Bitcoin commitment transaction
- pub fn built_transaction(&self) -> &BuiltCommitmentTransaction {
+ pub fn built_transaction(&self) -> &'a BuiltCommitmentTransaction {
&self.inner.built
}
/// The pre-calculated transaction creation public keys.
- pub fn keys(&self) -> &TxCreationKeys {
+ pub fn keys(&self) -> &'a TxCreationKeys {
&self.inner.keys
}
/// Should anchors be used.
- pub fn opt_anchors(&self) -> bool {
- self.opt_anchors.is_some()
+ pub fn channel_type_features(&self) -> &'a ChannelTypeFeatures {
+ &self.inner.channel_type_features
}
/// Get a signature for each HTLC which was included in the commitment transaction (ie for
/// The returned Vec has one entry for each HTLC, and in the same order.
///
/// This function is only valid in the holder commitment context, it always uses EcdsaSighashType::All.
- pub fn get_htlc_sigs<T: secp256k1::Signing>(&self, htlc_base_key: &SecretKey, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1<T>) -> Result<Vec<Signature>, ()> {
+ pub fn get_htlc_sigs<T: secp256k1::Signing, ES: Deref>(
+ &self, htlc_base_key: &SecretKey, channel_parameters: &DirectedChannelTransactionParameters,
+ entropy_source: &ES, secp_ctx: &Secp256k1<T>,
+ ) -> Result<Vec<Signature>, ()> where ES::Target: EntropySource {
let inner = self.inner;
let keys = &inner.keys;
let txid = inner.built.txid;
for this_htlc in inner.htlcs.iter() {
assert!(this_htlc.transaction_output_index.is_some());
- let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), self.opt_non_zero_fee_anchors.is_some(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
let sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).segwit_signature_hash(0, &htlc_redeemscript, this_htlc.amount_msat / 1000, EcdsaSighashType::All).unwrap()[..]);
- ret.push(sign(secp_ctx, &sighash, &holder_htlc_key));
+ ret.push(sign_with_aux_rand(secp_ctx, &sighash, &holder_htlc_key, entropy_source));
}
Ok(ret)
}
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
if this_htlc.offered && preimage.is_some() { unreachable!(); }
- let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), self.opt_non_zero_fee_anchors.is_some(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
+ let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
- let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
+ let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness(
- signature, counterparty_signature, preimage, &htlc_redeemscript, self.opt_anchors(),
+ signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
);
htlc_tx
}
+
+ /// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to
+ /// the broadcaster, in the built transaction, if any exists.
+ ///
+ /// There are two cases where this may return `None`:
+ /// - The balance of the revokeable output is below the dust limit (only found on commitments
+ /// early in the channel's lifetime, i.e. before the channel reserve is met).
+ /// - This commitment was created before LDK 0.0.117. In this case, the
+ /// commitment transaction previously didn't contain enough information to locate the
+ /// revokeable output.
+ pub fn revokeable_output_index(&self) -> Option<usize> {
+ let revokeable_redeemscript = get_revokeable_redeemscript(
+ &self.keys.revocation_key,
+ self.to_broadcaster_delay?,
+ &self.keys.broadcaster_delayed_payment_key,
+ );
+ let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh();
+ let outputs = &self.inner.built.transaction.output;
+ outputs.iter().enumerate()
+ .find(|(_, out)| out.script_pubkey == revokeable_p2wsh)
+ .map(|(idx, _)| idx)
+ }
+
+ /// Helper method to build an unsigned justice transaction spending the revokeable
+ /// `to_local` output to a destination script. Fee estimation accounts for the expected
+ /// revocation witness data that will be added when signed.
+ ///
+ /// This method will error if the given fee rate results in a fee greater than the value
+ /// of the output being spent, or if there exists no revokeable `to_local` output on this
+ /// commitment transaction. See [`Self::revokeable_output_index`] for more details.
+ ///
+ /// The built transaction will allow fee bumping with RBF, and this method takes
+ /// `feerate_per_kw` as an input such that multiple copies of a justice transaction at different
+ /// fee rates may be built.
+ pub fn build_to_local_justice_tx(&self, feerate_per_kw: u64, destination_script: Script)
+ -> Result<Transaction, ()> {
+ let output_idx = self.revokeable_output_index().ok_or(())?;
+ let input = vec![TxIn {
+ previous_output: OutPoint {
+ txid: self.trust().txid(),
+ vout: output_idx as u32,
+ },
+ script_sig: Script::new(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+ witness: Witness::new(),
+ }];
+ let value = self.inner.built.transaction.output[output_idx].value;
+ let output = vec![TxOut {
+ script_pubkey: destination_script,
+ value,
+ }];
+ let mut justice_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input,
+ output,
+ };
+ let weight = justice_tx.weight() as u64 + WEIGHT_REVOKED_OUTPUT;
+ let fee = fee_for_weight(feerate_per_kw as u32, weight);
+ justice_tx.output[0].value = value.checked_sub(fee).ok_or(())?;
+ Ok(justice_tx)
+ }
+
}
/// Commitment transaction numbers which appear in the transactions themselves are XOR'd with a
#[cfg(test)]
mod tests {
- use super::CounterpartyCommitmentSecrets;
+ use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
use crate::{hex, chain};
use crate::prelude::*;
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use crate::util::test_utils;
- use crate::chain::keysinterface::{ChannelSigner, SignerProvider};
- use bitcoin::{Network, Txid};
+ use crate::sign::{ChannelSigner, SignerProvider};
+ use bitcoin::{Network, Txid, Script};
use bitcoin::hashes::Hash;
use crate::ln::PaymentHash;
use bitcoin::hashes::hex::ToHex;
use bitcoin::util::address::Payload;
use bitcoin::PublicKey as BitcoinPublicKey;
+ use crate::ln::features::ChannelTypeFeatures;
+
+ struct TestCommitmentTxBuilder {
+ commitment_number: u64,
+ holder_funding_pubkey: PublicKey,
+ counterparty_funding_pubkey: PublicKey,
+ keys: TxCreationKeys,
+ feerate_per_kw: u32,
+ htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>,
+ channel_parameters: ChannelTransactionParameters,
+ counterparty_pubkeys: ChannelPublicKeys,
+ }
+
+ impl TestCommitmentTxBuilder {
+ fn new() -> Self {
+ 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.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
+ let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
+ 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().clone();
+ let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
+ 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: Txid::all_zeros(), index: 0 }),
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
+ };
+ let htlcs_with_aux = Vec::new();
+
+ Self {
+ commitment_number: 0,
+ holder_funding_pubkey: holder_pubkeys.funding_pubkey,
+ counterparty_funding_pubkey: counterparty_pubkeys.funding_pubkey,
+ keys,
+ feerate_per_kw: 1,
+ htlcs_with_aux,
+ channel_parameters,
+ counterparty_pubkeys,
+ }
+ }
+
+ fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction {
+ CommitmentTransaction::new_with_auxiliary_htlc_data(
+ self.commitment_number, to_broadcaster_sats, to_countersignatory_sats,
+ self.holder_funding_pubkey.clone(),
+ self.counterparty_funding_pubkey.clone(),
+ self.keys.clone(), self.feerate_per_kw,
+ &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable()
+ )
+ }
+ }
#[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.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
- let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
- 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);
- let mut 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: Txid::all_zeros(), index: 0 }),
- opt_anchors: None,
- opt_non_zero_fee_anchors: None,
- };
-
- let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
+ let mut builder = TestCommitmentTxBuilder::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()
- );
+ let tx = builder.build(1000, 2000);
assert_eq!(tx.built.transaction.output.len(), 2);
- assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(builder.counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
// 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()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ let tx = builder.build(1000, 2000);
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());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&builder.counterparty_pubkeys.payment_point).to_v0_p2wsh());
// 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()
- );
+ let tx = builder.build(3000, 0);
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()
- );
+ let tx = builder.build(0, 3000);
assert_eq!(tx.built.transaction.output.len(), 2);
let received_htlc = HTLCOutputInCommitment {
};
// Generate broadcaster output and received and offered HTLC outputs, w/o anchors
- let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
- 0, 3000, 0,
- false,
- holder_pubkeys.funding_pubkey,
- counterparty_pubkeys.funding_pubkey,
- keys.clone(), 1,
- &mut vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
- &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
+ let keys = &builder.keys.clone();
assert_eq!(tx.built.transaction.output.len(), 3);
- assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, false, &keys).to_v0_p2wsh());
- assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, false, &keys).to_v0_p2wsh());
- assert_eq!(get_htlc_redeemscript(&received_htlc, false, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh().to_hex(),
"0020e43a7c068553003fe68fcae424fb7b28ec5ce48cd8b6744b3945631389bad2fb");
- assert_eq!(get_htlc_redeemscript(&offered_htlc, false, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh().to_hex(),
"0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d");
// Generate broadcaster output and received and offered HTLC outputs, with anchors
- channel_parameters.opt_anchors = Some(());
- 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![(received_htlc.clone(), ()), (offered_htlc.clone(), ())],
- &channel_parameters.as_holder_broadcastable()
- );
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
assert_eq!(tx.built.transaction.output.len(), 5);
- assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, true, &keys).to_v0_p2wsh());
- assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, true, &keys).to_v0_p2wsh());
- assert_eq!(get_htlc_redeemscript(&received_htlc, true, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh().to_hex(),
"0020b70d0649c72b38756885c7a30908d912a7898dd5d79457a7280b8e9a20f3f2bc");
- assert_eq!(get_htlc_redeemscript(&offered_htlc, true, &keys).to_v0_p2wsh().to_hex(),
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh().to_hex(),
"002087a3faeb1950a469c0e2db4a79b093a41b9526e5a6fc6ef5cb949bde3be379c7");
}
+ #[test]
+ fn test_finding_revokeable_output_index() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), Some(0));
+
+ // Revokeable output present (but to_broadcaster_delay missing)
+ let tx = CommitmentTransaction { to_broadcaster_delay: None, ..tx };
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+ }
+
+ #[test]
+ fn test_building_to_local_justice_tx() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert!(tx.trust().build_to_local_justice_tx(253, Script::new()).is_err());
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ // Too high feerate
+ assert!(tx.trust().build_to_local_justice_tx(100_000, Script::new()).is_err());
+
+ // Generate a random public key for destination script
+ let secret_key = SecretKey::from_slice(
+ &hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
+ .unwrap()[..]).unwrap();
+ let pubkey_hash = BitcoinPublicKey::new(
+ PublicKey::from_secret_key(&Secp256k1::new(), &secret_key)).wpubkey_hash().unwrap();
+ let destination_script = Script::new_v0_p2wpkh(&pubkey_hash);
+
+ let justice_tx = tx.trust().build_to_local_justice_tx(253, destination_script.clone()).unwrap();
+ assert_eq!(justice_tx.input.len(), 1);
+ assert_eq!(justice_tx.input[0].previous_output.txid, tx.built.transaction.txid());
+ assert_eq!(justice_tx.input[0].previous_output.vout, tx.trust().revokeable_output_index().unwrap() as u32);
+ assert!(justice_tx.input[0].sequence.is_rbf());
+
+ assert_eq!(justice_tx.output.len(), 1);
+ assert!(justice_tx.output[0].value < 1000);
+ assert_eq!(justice_tx.output[0].script_pubkey, destination_script);
+ }
+
#[test]
fn test_per_commitment_storage() {
// Test vectors from BOLT 3: