mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 23:08:36 +01:00
Introduce CommitmentTransaction, ChannelTransactionParameters
CommitmentTransaction maintains the per-commitment transaction fields needed to construct the associated bitcoin transactions (commitment, HTLC). It replaces passing around of Bitcoin transactions. The ChannelKeys API is modified accordingly. By regenerating the transaction when implementing a validating external signer, this allows a higher level of assurance that all relevant aspects of the transactions were checked for policy violations. ChannelTransactionParameters replaces passing around of individual per-channel fields that are needed to construct Bitcoin transactions. Eliminate ChannelStaticData in favor of ChannelTransactionParameters. Use counterparty txid instead of tx in channelmonitor update.
This commit is contained in:
parent
a294a3f906
commit
2de29ae049
9 changed files with 1232 additions and 841 deletions
|
@ -12,6 +12,7 @@ Still missing tons of error-handling. See GitHub issues for suggested projects i
|
|||
|
||||
[features]
|
||||
fuzztarget = ["bitcoin/fuzztarget"]
|
||||
# Internal test utilities exposed to other repo crates
|
||||
_test_utils = ["hex", "regex"]
|
||||
# Unlog messages superior at targeted level.
|
||||
max_level_off = []
|
||||
|
|
|
@ -27,7 +27,6 @@ use bitcoin::blockdata::transaction::{TxOut,Transaction};
|
|||
use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
|
||||
use bitcoin::blockdata::script::{Script, Builder};
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::consensus::encode;
|
||||
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
|
@ -39,7 +38,7 @@ use bitcoin::secp256k1;
|
|||
|
||||
use ln::msgs::DecodeError;
|
||||
use ln::chan_utils;
|
||||
use ln::chan_utils::{CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HolderCommitmentTransaction, HTLCType};
|
||||
use ln::chan_utils::{CounterpartyCommitmentSecrets, HTLCOutputInCommitment, HTLCType, ChannelTransactionParameters, HolderCommitmentTransaction};
|
||||
use ln::channelmanager::{HTLCSource, PaymentPreimage, PaymentHash};
|
||||
use ln::onchaintx::{OnchainTxHandler, InputDescriptors};
|
||||
use chain::chaininterface::{BroadcasterInterface, FeeEstimator};
|
||||
|
@ -252,6 +251,7 @@ pub(crate) const ANTI_REORG_DELAY: u32 = 6;
|
|||
/// end up force-closing the channel on us to claim it.
|
||||
pub(crate) const HTLC_FAIL_BACK_BUFFER: u32 = CLTV_CLAIM_BUFFER + LATENCY_GRACE_PERIOD_BLOCKS;
|
||||
|
||||
// TODO(devrandom) replace this with HolderCommitmentTransaction
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct HolderSignedTx {
|
||||
/// txid of the transaction in tx, just used to make comparison faster
|
||||
|
@ -493,7 +493,7 @@ pub(crate) enum ChannelMonitorUpdateStep {
|
|||
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
|
||||
},
|
||||
LatestCounterpartyCommitmentTXInfo {
|
||||
unsigned_commitment_tx: Transaction, // TODO: We should actually only need the txid here
|
||||
commitment_txid: Txid,
|
||||
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>,
|
||||
commitment_number: u64,
|
||||
their_revocation_point: PublicKey,
|
||||
|
@ -527,9 +527,9 @@ impl Writeable for ChannelMonitorUpdateStep {
|
|||
source.write(w)?;
|
||||
}
|
||||
}
|
||||
&ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { ref unsigned_commitment_tx, ref htlc_outputs, ref commitment_number, ref their_revocation_point } => {
|
||||
&ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, ref htlc_outputs, ref commitment_number, ref their_revocation_point } => {
|
||||
1u8.write(w)?;
|
||||
unsigned_commitment_tx.write(w)?;
|
||||
commitment_txid.write(w)?;
|
||||
commitment_number.write(w)?;
|
||||
their_revocation_point.write(w)?;
|
||||
(htlc_outputs.len() as u64).write(w)?;
|
||||
|
@ -573,7 +573,7 @@ impl Readable for ChannelMonitorUpdateStep {
|
|||
},
|
||||
1u8 => {
|
||||
Ok(ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo {
|
||||
unsigned_commitment_tx: Readable::read(r)?,
|
||||
commitment_txid: Readable::read(r)?,
|
||||
commitment_number: Readable::read(r)?,
|
||||
their_revocation_point: Readable::read(r)?,
|
||||
htlc_outputs: {
|
||||
|
@ -948,11 +948,11 @@ impl<ChanSigner: ChannelKeys + Writeable> ChannelMonitor<ChanSigner> {
|
|||
|
||||
impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
||||
pub(crate) fn new(keys: ChanSigner, shutdown_pubkey: &PublicKey,
|
||||
on_counterparty_tx_csv: u16, destination_script: &Script, funding_info: (OutPoint, Script),
|
||||
counterparty_htlc_base_key: &PublicKey, counterparty_delayed_payment_base_key: &PublicKey,
|
||||
on_holder_tx_csv: u16, funding_redeemscript: Script, channel_value_satoshis: u64,
|
||||
commitment_transaction_number_obscure_factor: u64,
|
||||
initial_holder_commitment_tx: HolderCommitmentTransaction) -> ChannelMonitor<ChanSigner> {
|
||||
on_counterparty_tx_csv: u16, destination_script: &Script, funding_info: (OutPoint, Script),
|
||||
channel_parameters: &ChannelTransactionParameters,
|
||||
funding_redeemscript: Script, channel_value_satoshis: u64,
|
||||
commitment_transaction_number_obscure_factor: u64,
|
||||
initial_holder_commitment_tx: HolderCommitmentTransaction) -> ChannelMonitor<ChanSigner> {
|
||||
|
||||
assert!(commitment_transaction_number_obscure_factor <= (1 << 48));
|
||||
let our_channel_close_key_hash = WPubkeyHash::hash(&shutdown_pubkey.serialize());
|
||||
|
@ -960,21 +960,32 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
let payment_key_hash = WPubkeyHash::hash(&keys.pubkeys().payment_point.serialize());
|
||||
let counterparty_payment_script = Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&payment_key_hash[..]).into_script();
|
||||
|
||||
let counterparty_tx_cache = CounterpartyCommitmentTransaction { counterparty_delayed_payment_base_key: *counterparty_delayed_payment_base_key, counterparty_htlc_base_key: *counterparty_htlc_base_key, on_counterparty_tx_csv, per_htlc: HashMap::new() };
|
||||
let counterparty_channel_parameters = channel_parameters.counterparty_parameters.as_ref().unwrap();
|
||||
let counterparty_delayed_payment_base_key = counterparty_channel_parameters.pubkeys.delayed_payment_basepoint;
|
||||
let counterparty_htlc_base_key = counterparty_channel_parameters.pubkeys.htlc_basepoint;
|
||||
let counterparty_tx_cache = CounterpartyCommitmentTransaction { counterparty_delayed_payment_base_key, counterparty_htlc_base_key, on_counterparty_tx_csv, per_htlc: HashMap::new() };
|
||||
|
||||
let mut onchain_tx_handler = OnchainTxHandler::new(destination_script.clone(), keys.clone(), on_holder_tx_csv);
|
||||
let mut onchain_tx_handler = OnchainTxHandler::new(destination_script.clone(), keys.clone(), channel_parameters.clone());
|
||||
|
||||
let holder_tx_sequence = initial_holder_commitment_tx.unsigned_tx.input[0].sequence as u64;
|
||||
let holder_tx_locktime = initial_holder_commitment_tx.unsigned_tx.lock_time as u64;
|
||||
let holder_commitment_tx = HolderSignedTx {
|
||||
txid: initial_holder_commitment_tx.txid(),
|
||||
revocation_key: initial_holder_commitment_tx.keys.revocation_key,
|
||||
a_htlc_key: initial_holder_commitment_tx.keys.broadcaster_htlc_key,
|
||||
b_htlc_key: initial_holder_commitment_tx.keys.countersignatory_htlc_key,
|
||||
delayed_payment_key: initial_holder_commitment_tx.keys.broadcaster_delayed_payment_key,
|
||||
per_commitment_point: initial_holder_commitment_tx.keys.per_commitment_point,
|
||||
feerate_per_kw: initial_holder_commitment_tx.feerate_per_kw,
|
||||
htlc_outputs: Vec::new(), // There are never any HTLCs in the initial commitment transactions
|
||||
let secp_ctx = Secp256k1::new();
|
||||
|
||||
// block for Rust 1.34 compat
|
||||
let (holder_commitment_tx, current_holder_commitment_number) = {
|
||||
let trusted_tx = initial_holder_commitment_tx.trust();
|
||||
let txid = trusted_tx.txid();
|
||||
|
||||
let tx_keys = trusted_tx.keys();
|
||||
let holder_commitment_tx = HolderSignedTx {
|
||||
txid,
|
||||
revocation_key: tx_keys.revocation_key,
|
||||
a_htlc_key: tx_keys.broadcaster_htlc_key,
|
||||
b_htlc_key: tx_keys.countersignatory_htlc_key,
|
||||
delayed_payment_key: tx_keys.broadcaster_delayed_payment_key,
|
||||
per_commitment_point: tx_keys.per_commitment_point,
|
||||
feerate_per_kw: trusted_tx.feerate_per_kw(),
|
||||
htlc_outputs: Vec::new(), // There are never any HTLCs in the initial commitment transactions
|
||||
};
|
||||
(holder_commitment_tx, trusted_tx.commitment_number())
|
||||
};
|
||||
onchain_tx_handler.provide_latest_holder_tx(initial_holder_commitment_tx);
|
||||
|
||||
|
@ -1000,7 +1011,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
channel_value_satoshis,
|
||||
their_cur_revocation_points: None,
|
||||
|
||||
on_holder_tx_csv,
|
||||
on_holder_tx_csv: counterparty_channel_parameters.selected_contest_delay,
|
||||
|
||||
commitment_secrets: CounterpartyCommitmentSecrets::new(),
|
||||
counterparty_claimable_outpoints: HashMap::new(),
|
||||
|
@ -1010,7 +1021,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
prev_holder_signed_commitment_tx: None,
|
||||
current_holder_commitment_tx: holder_commitment_tx,
|
||||
current_counterparty_commitment_number: 1 << 48,
|
||||
current_holder_commitment_number: 0xffff_ffff_ffff - ((((holder_tx_sequence & 0xffffff) << 3*8) | (holder_tx_locktime as u64 & 0xffffff)) ^ commitment_transaction_number_obscure_factor),
|
||||
current_holder_commitment_number,
|
||||
|
||||
payment_preimages: HashMap::new(),
|
||||
pending_monitor_events: Vec::new(),
|
||||
|
@ -1025,7 +1036,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
holder_tx_signed: false,
|
||||
|
||||
last_block_hash: Default::default(),
|
||||
secp_ctx: Secp256k1::new(),
|
||||
secp_ctx,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1084,7 +1095,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
/// The monitor watches for it to be broadcasted and then uses the HTLC information (and
|
||||
/// possibly future revocation/preimage information) to claim outputs where possible.
|
||||
/// We cache also the mapping hash:commitment number to lighten pruning of old preimages by watchtowers.
|
||||
pub(crate) fn provide_latest_counterparty_commitment_tx_info<L: Deref>(&mut self, unsigned_commitment_tx: &Transaction, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>, commitment_number: u64, their_revocation_point: PublicKey, logger: &L) where L::Target: Logger {
|
||||
pub(crate) fn provide_latest_counterparty_commitment_tx<L: Deref>(&mut self, txid: Txid, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>, commitment_number: u64, their_revocation_point: PublicKey, logger: &L) where L::Target: Logger {
|
||||
// TODO: Encrypt the htlc_outputs data with the single-hash of the commitment transaction
|
||||
// so that a remote monitor doesn't learn anything unless there is a malicious close.
|
||||
// (only maybe, sadly we cant do the same for local info, as we need to be aware of
|
||||
|
@ -1093,12 +1104,10 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
self.counterparty_hash_commitment_number.insert(htlc.payment_hash, commitment_number);
|
||||
}
|
||||
|
||||
let new_txid = unsigned_commitment_tx.txid();
|
||||
log_trace!(logger, "Tracking new counterparty commitment transaction with txid {} at commitment number {} with {} HTLC outputs", new_txid, commitment_number, htlc_outputs.len());
|
||||
log_trace!(logger, "New potential counterparty commitment transaction: {}", encode::serialize_hex(unsigned_commitment_tx));
|
||||
log_trace!(logger, "Tracking new counterparty commitment transaction with txid {} at commitment number {} with {} HTLC outputs", txid, commitment_number, htlc_outputs.len());
|
||||
self.prev_counterparty_commitment_txid = self.current_counterparty_commitment_txid.take();
|
||||
self.current_counterparty_commitment_txid = Some(new_txid);
|
||||
self.counterparty_claimable_outpoints.insert(new_txid, htlc_outputs.clone());
|
||||
self.current_counterparty_commitment_txid = Some(txid);
|
||||
self.counterparty_claimable_outpoints.insert(txid, htlc_outputs.clone());
|
||||
self.current_counterparty_commitment_number = commitment_number;
|
||||
//TODO: Merge this into the other per-counterparty-transaction output storage stuff
|
||||
match self.their_cur_revocation_points {
|
||||
|
@ -1125,7 +1134,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
htlcs.push(htlc.0);
|
||||
}
|
||||
}
|
||||
self.counterparty_tx_cache.per_htlc.insert(new_txid, htlcs);
|
||||
self.counterparty_tx_cache.per_htlc.insert(txid, htlcs);
|
||||
}
|
||||
|
||||
/// Informs this monitor of the latest holder (ie broadcastable) commitment transaction. The
|
||||
|
@ -1133,22 +1142,25 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
/// is important that any clones of this channel monitor (including remote clones) by kept
|
||||
/// up-to-date as our holder commitment transaction is updated.
|
||||
/// Panics if set_on_holder_tx_csv has never been called.
|
||||
fn provide_latest_holder_commitment_tx_info(&mut self, commitment_tx: HolderCommitmentTransaction, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>) -> Result<(), MonitorUpdateError> {
|
||||
let txid = commitment_tx.txid();
|
||||
let sequence = commitment_tx.unsigned_tx.input[0].sequence as u64;
|
||||
let locktime = commitment_tx.unsigned_tx.lock_time as u64;
|
||||
let mut new_holder_commitment_tx = HolderSignedTx {
|
||||
txid,
|
||||
revocation_key: commitment_tx.keys.revocation_key,
|
||||
a_htlc_key: commitment_tx.keys.broadcaster_htlc_key,
|
||||
b_htlc_key: commitment_tx.keys.countersignatory_htlc_key,
|
||||
delayed_payment_key: commitment_tx.keys.broadcaster_delayed_payment_key,
|
||||
per_commitment_point: commitment_tx.keys.per_commitment_point,
|
||||
feerate_per_kw: commitment_tx.feerate_per_kw,
|
||||
htlc_outputs,
|
||||
fn provide_latest_holder_commitment_tx(&mut self, holder_commitment_tx: HolderCommitmentTransaction, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>) -> Result<(), MonitorUpdateError> {
|
||||
// block for Rust 1.34 compat
|
||||
let mut new_holder_commitment_tx = {
|
||||
let trusted_tx = holder_commitment_tx.trust();
|
||||
let txid = trusted_tx.txid();
|
||||
let tx_keys = trusted_tx.keys();
|
||||
self.current_holder_commitment_number = trusted_tx.commitment_number();
|
||||
HolderSignedTx {
|
||||
txid,
|
||||
revocation_key: tx_keys.revocation_key,
|
||||
a_htlc_key: tx_keys.broadcaster_htlc_key,
|
||||
b_htlc_key: tx_keys.countersignatory_htlc_key,
|
||||
delayed_payment_key: tx_keys.broadcaster_delayed_payment_key,
|
||||
per_commitment_point: tx_keys.per_commitment_point,
|
||||
feerate_per_kw: trusted_tx.feerate_per_kw(),
|
||||
htlc_outputs,
|
||||
}
|
||||
};
|
||||
self.onchain_tx_handler.provide_latest_holder_tx(commitment_tx);
|
||||
self.current_holder_commitment_number = 0xffff_ffff_ffff - ((((sequence & 0xffffff) << 3*8) | (locktime as u64 & 0xffffff)) ^ self.commitment_transaction_number_obscure_factor);
|
||||
self.onchain_tx_handler.provide_latest_holder_tx(holder_commitment_tx);
|
||||
mem::swap(&mut new_holder_commitment_tx, &mut self.current_holder_commitment_tx);
|
||||
self.prev_holder_signed_commitment_tx = Some(new_holder_commitment_tx);
|
||||
if self.holder_tx_signed {
|
||||
|
@ -1239,11 +1251,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
|
|||
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs } => {
|
||||
log_trace!(logger, "Updating ChannelMonitor with latest holder commitment transaction info");
|
||||
if self.lockdown_from_offchain { panic!(); }
|
||||
self.provide_latest_holder_commitment_tx_info(commitment_tx.clone(), htlc_outputs.clone())?
|
||||
},
|
||||
ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { unsigned_commitment_tx, htlc_outputs, commitment_number, their_revocation_point } => {
|
||||
self.provide_latest_holder_commitment_tx(commitment_tx.clone(), htlc_outputs.clone())?
|
||||
}
|
||||
ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { commitment_txid, htlc_outputs, commitment_number, their_revocation_point } => {
|
||||
log_trace!(logger, "Updating ChannelMonitor with latest counterparty commitment transaction info");
|
||||
self.provide_latest_counterparty_commitment_tx_info(&unsigned_commitment_tx, htlc_outputs.clone(), *commitment_number, *their_revocation_point, logger)
|
||||
self.provide_latest_counterparty_commitment_tx(*commitment_txid, htlc_outputs.clone(), *commitment_number, *their_revocation_point, logger)
|
||||
},
|
||||
ChannelMonitorUpdateStep::PaymentPreimage { payment_preimage } => {
|
||||
log_trace!(logger, "Updating ChannelMonitor with payment preimage");
|
||||
|
@ -2589,7 +2601,7 @@ mod tests {
|
|||
use ln::channelmanager::{PaymentPreimage, PaymentHash};
|
||||
use ln::onchaintx::{OnchainTxHandler, InputDescriptors};
|
||||
use ln::chan_utils;
|
||||
use ln::chan_utils::{HTLCOutputInCommitment, HolderCommitmentTransaction};
|
||||
use ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, ChannelTransactionParameters, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
|
||||
use util::test_utils::{TestLogger, TestBroadcaster, TestFeeEstimator};
|
||||
use bitcoin::secp256k1::key::{SecretKey,PublicKey};
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
|
@ -2662,20 +2674,39 @@ mod tests {
|
|||
(0, 0)
|
||||
);
|
||||
|
||||
let counterparty_pubkeys = ChannelPublicKeys {
|
||||
funding_pubkey: PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[44; 32]).unwrap()),
|
||||
revocation_basepoint: PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[45; 32]).unwrap()),
|
||||
payment_point: PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[46; 32]).unwrap()),
|
||||
delayed_payment_basepoint: PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[47; 32]).unwrap()),
|
||||
htlc_basepoint: PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[48; 32]).unwrap())
|
||||
};
|
||||
let funding_outpoint = OutPoint { txid: Default::default(), index: u16::max_value() };
|
||||
let channel_parameters = ChannelTransactionParameters {
|
||||
holder_pubkeys: keys.holder_channel_pubkeys.clone(),
|
||||
holder_selected_contest_delay: 66,
|
||||
is_outbound_from_holder: true,
|
||||
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
|
||||
pubkeys: counterparty_pubkeys,
|
||||
selected_contest_delay: 67,
|
||||
}),
|
||||
funding_outpoint: Some(funding_outpoint),
|
||||
};
|
||||
// Prune with one old state and a holder commitment tx holding a few overlaps with the
|
||||
// old state.
|
||||
let mut monitor = ChannelMonitor::new(keys,
|
||||
&PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()), 0, &Script::new(),
|
||||
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
|
||||
&PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[44; 32]).unwrap()),
|
||||
&PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[45; 32]).unwrap()),
|
||||
10, Script::new(), 46, 0, HolderCommitmentTransaction::dummy());
|
||||
&PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()), 0, &Script::new(),
|
||||
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
|
||||
&channel_parameters,
|
||||
Script::new(), 46, 0,
|
||||
HolderCommitmentTransaction::dummy());
|
||||
|
||||
monitor.provide_latest_holder_commitment_tx_info(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..10])).unwrap();
|
||||
monitor.provide_latest_counterparty_commitment_tx_info(&dummy_tx, preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key, &logger);
|
||||
monitor.provide_latest_counterparty_commitment_tx_info(&dummy_tx, preimages_slice_to_htlc_outputs!(preimages[15..20]), 281474976710654, dummy_key, &logger);
|
||||
monitor.provide_latest_counterparty_commitment_tx_info(&dummy_tx, preimages_slice_to_htlc_outputs!(preimages[17..20]), 281474976710653, dummy_key, &logger);
|
||||
monitor.provide_latest_counterparty_commitment_tx_info(&dummy_tx, preimages_slice_to_htlc_outputs!(preimages[18..20]), 281474976710652, dummy_key, &logger);
|
||||
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..10])).unwrap();
|
||||
let dummy_txid = dummy_tx.txid();
|
||||
monitor.provide_latest_counterparty_commitment_tx(dummy_txid, preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key, &logger);
|
||||
monitor.provide_latest_counterparty_commitment_tx(dummy_txid, preimages_slice_to_htlc_outputs!(preimages[15..20]), 281474976710654, dummy_key, &logger);
|
||||
monitor.provide_latest_counterparty_commitment_tx(dummy_txid, preimages_slice_to_htlc_outputs!(preimages[17..20]), 281474976710653, dummy_key, &logger);
|
||||
monitor.provide_latest_counterparty_commitment_tx(dummy_txid, preimages_slice_to_htlc_outputs!(preimages[18..20]), 281474976710652, dummy_key, &logger);
|
||||
for &(ref preimage, ref hash) in preimages.iter() {
|
||||
monitor.provide_payment_preimage(hash, preimage, &broadcaster, &fee_estimator, &logger);
|
||||
}
|
||||
|
@ -2697,7 +2728,7 @@ mod tests {
|
|||
|
||||
// Now update holder commitment tx info, pruning only element 18 as we still care about the
|
||||
// previous commitment tx's preimages too
|
||||
monitor.provide_latest_holder_commitment_tx_info(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..5])).unwrap();
|
||||
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..5])).unwrap();
|
||||
secret[0..32].clone_from_slice(&hex::decode("2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8").unwrap());
|
||||
monitor.provide_secret(281474976710653, secret.clone()).unwrap();
|
||||
assert_eq!(monitor.payment_preimages.len(), 12);
|
||||
|
@ -2705,7 +2736,7 @@ mod tests {
|
|||
test_preimages_exist!(&preimages[18..20], monitor);
|
||||
|
||||
// But if we do it again, we'll prune 5-10
|
||||
monitor.provide_latest_holder_commitment_tx_info(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..3])).unwrap();
|
||||
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..3])).unwrap();
|
||||
secret[0..32].clone_from_slice(&hex::decode("27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116").unwrap());
|
||||
monitor.provide_secret(281474976710652, secret.clone()).unwrap();
|
||||
assert_eq!(monitor.payment_preimages.len(), 5);
|
||||
|
|
|
@ -33,7 +33,7 @@ use util::ser::{Writeable, Writer, Readable};
|
|||
|
||||
use chain::transaction::OutPoint;
|
||||
use ln::chan_utils;
|
||||
use ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, PreCalculatedTxCreationKeys};
|
||||
use ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction};
|
||||
use ln::msgs::UnsignedChannelAnnouncement;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
@ -79,7 +79,7 @@ pub enum SpendableOutputDescriptor {
|
|||
///
|
||||
/// To derive the revocation_pubkey provided here (which is used in the witness
|
||||
/// script generation), you must pass the counterparty revocation_basepoint (which appears in the
|
||||
/// call to ChannelKeys::on_accept) and the provided per_commitment point
|
||||
/// call to ChannelKeys::ready_channel) and the provided per_commitment point
|
||||
/// to chan_utils::derive_public_revocation_key.
|
||||
///
|
||||
/// The witness script which is hashed and included in the output script_pubkey may be
|
||||
|
@ -231,40 +231,34 @@ pub trait ChannelKeys : Send+Clone {
|
|||
/// Note that if signing fails or is rejected, the channel will be force-closed.
|
||||
//
|
||||
// TODO: Document the things someone using this interface should enforce before signing.
|
||||
// TODO: Add more input vars to enable better checking (preferably removing commitment_tx and
|
||||
// making the callee generate it via some util function we expose)!
|
||||
fn sign_counterparty_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, feerate_per_kw: u32, commitment_tx: &Transaction, keys: &PreCalculatedTxCreationKeys, htlcs: &[&HTLCOutputInCommitment], secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()>;
|
||||
fn sign_counterparty_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()>;
|
||||
|
||||
/// Create a signature for a holder's commitment transaction. This will only ever be called with
|
||||
/// the same holder_commitment_tx (or a copy thereof), though there are currently no guarantees
|
||||
/// the same commitment_tx (or a copy thereof), though there are currently no guarantees
|
||||
/// that it will not be called multiple times.
|
||||
/// An external signer implementation should check that the commitment has not been revoked.
|
||||
//
|
||||
// TODO: Document the things someone using this interface should enforce before signing.
|
||||
// TODO: Add more input vars to enable better checking (preferably removing commitment_tx and
|
||||
fn sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
|
||||
fn sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
|
||||
|
||||
/// Same as sign_holder_commitment, but exists only for tests to get access to holder commitment
|
||||
/// transactions which will be broadcasted later, after the channel has moved on to a newer
|
||||
/// state. Thus, needs its own method as sign_holder_commitment may enforce that we only ever
|
||||
/// get called once.
|
||||
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
|
||||
fn unsafe_sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
|
||||
fn unsafe_sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
|
||||
|
||||
/// Create a signature for each HTLC transaction spending a holder's commitment transaction.
|
||||
///
|
||||
/// Unlike sign_holder_commitment, this may be called multiple times with *different*
|
||||
/// holder_commitment_tx values. While this will never be called with a revoked
|
||||
/// holder_commitment_tx, it is possible that it is called with the second-latest
|
||||
/// holder_commitment_tx (only if we haven't yet revoked it) if some watchtower/secondary
|
||||
/// commitment_tx values. While this will never be called with a revoked
|
||||
/// commitment_tx, it is possible that it is called with the second-latest
|
||||
/// commitment_tx (only if we haven't yet revoked it) if some watchtower/secondary
|
||||
/// ChannelMonitor decided to broadcast before it had been updated to the latest.
|
||||
///
|
||||
/// Either an Err should be returned, or a Vec with one entry for each HTLC which exists in
|
||||
/// holder_commitment_tx. For those HTLCs which have transaction_output_index set to None
|
||||
/// (implying they were considered dust at the time the commitment transaction was negotiated),
|
||||
/// a corresponding None should be included in the return value. All other positions in the
|
||||
/// return value must contain a signature.
|
||||
fn sign_holder_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()>;
|
||||
/// commitment_tx.
|
||||
fn sign_holder_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Vec<Signature>, ()>;
|
||||
|
||||
/// Create a signature for the given input in a transaction spending an HTLC or commitment
|
||||
/// transaction output when our counterparty broadcasts an old state.
|
||||
|
@ -319,13 +313,17 @@ pub trait ChannelKeys : Send+Clone {
|
|||
/// protocol.
|
||||
fn sign_channel_announcement<T: secp256k1::Signing>(&self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
|
||||
|
||||
/// Set the counterparty channel basepoints and counterparty_selected/holder_selected_contest_delay.
|
||||
/// This is done immediately on incoming channels and as soon as the channel is accepted on outgoing channels.
|
||||
/// Set the counterparty static channel data, including basepoints,
|
||||
/// counterparty_selected/holder_selected_contest_delay and funding outpoint.
|
||||
/// This is done as soon as the funding outpoint is known. Since these are static channel data,
|
||||
/// they MUST NOT be allowed to change to different values once set.
|
||||
///
|
||||
/// channel_parameters.is_populated() MUST be true.
|
||||
///
|
||||
/// We bind holder_selected_contest_delay late here for API convenience.
|
||||
///
|
||||
/// Will be called before any signatures are applied.
|
||||
fn on_accept(&mut self, channel_points: &ChannelPublicKeys, counterparty_selected_contest_delay: u16, holder_selected_contest_delay: u16);
|
||||
fn ready_channel(&mut self, channel_parameters: &ChannelTransactionParameters);
|
||||
}
|
||||
|
||||
/// A trait to describe an object which can get user secrets and key material.
|
||||
|
@ -348,27 +346,11 @@ pub trait KeysInterface: Send + Sync {
|
|||
fn get_secure_random_bytes(&self) -> [u8; 32];
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Holds late-bound channel data.
|
||||
/// This data is available after the channel is known to be accepted, either
|
||||
/// when receiving an open_channel for an inbound channel or when
|
||||
/// receiving accept_channel for an outbound channel.
|
||||
struct AcceptedChannelData {
|
||||
/// Counterparty public keys and base points
|
||||
counterparty_channel_pubkeys: ChannelPublicKeys,
|
||||
/// The contest_delay value specified by our counterparty and applied on holder-broadcastable
|
||||
/// transactions, ie the amount of time that we have to wait to recover our funds if we
|
||||
/// broadcast a transaction. You'll likely want to pass this to the
|
||||
/// ln::chan_utils::build*_transaction functions when signing holder's transactions.
|
||||
counterparty_selected_contest_delay: u16,
|
||||
/// The contest_delay value specified by us and applied on transactions broadcastable
|
||||
/// by our counterparty, ie the amount of time that they have to wait to recover their funds
|
||||
/// if they broadcast a transaction.
|
||||
holder_selected_contest_delay: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// A simple implementation of ChannelKeys that just keeps the private keys in memory.
|
||||
///
|
||||
/// This implementation performs no policy checks and is insufficient by itself as
|
||||
/// a secure external signer.
|
||||
pub struct InMemoryChannelKeys {
|
||||
/// Private key of anchor tx
|
||||
pub funding_key: SecretKey,
|
||||
|
@ -385,7 +367,7 @@ pub struct InMemoryChannelKeys {
|
|||
/// Holder public keys and basepoints
|
||||
pub(crate) holder_channel_pubkeys: ChannelPublicKeys,
|
||||
/// Counterparty public keys and counterparty/holder selected_contest_delay, populated on channel acceptance
|
||||
accepted_channel_data: Option<AcceptedChannelData>,
|
||||
channel_parameters: Option<ChannelTransactionParameters>,
|
||||
/// The total value of this channel
|
||||
channel_value_satoshis: u64,
|
||||
/// Key derivation parameters
|
||||
|
@ -417,7 +399,7 @@ impl InMemoryChannelKeys {
|
|||
commitment_seed,
|
||||
channel_value_satoshis,
|
||||
holder_channel_pubkeys,
|
||||
accepted_channel_data: None,
|
||||
channel_parameters: None,
|
||||
key_derivation_params,
|
||||
}
|
||||
}
|
||||
|
@ -439,21 +421,36 @@ impl InMemoryChannelKeys {
|
|||
}
|
||||
|
||||
/// Counterparty pubkeys.
|
||||
/// Will panic if on_accept wasn't called.
|
||||
pub fn counterparty_pubkeys(&self) -> &ChannelPublicKeys { &self.accepted_channel_data.as_ref().unwrap().counterparty_channel_pubkeys }
|
||||
/// Will panic if ready_channel wasn't called.
|
||||
pub fn counterparty_pubkeys(&self) -> &ChannelPublicKeys { &self.get_channel_parameters().counterparty_parameters.as_ref().unwrap().pubkeys }
|
||||
|
||||
/// The contest_delay value specified by our counterparty and applied on holder-broadcastable
|
||||
/// transactions, ie the amount of time that we have to wait to recover our funds if we
|
||||
/// broadcast a transaction. You'll likely want to pass this to the
|
||||
/// ln::chan_utils::build*_transaction functions when signing holder's transactions.
|
||||
/// Will panic if on_accept wasn't called.
|
||||
pub fn counterparty_selected_contest_delay(&self) -> u16 { self.accepted_channel_data.as_ref().unwrap().counterparty_selected_contest_delay }
|
||||
/// broadcast a transaction.
|
||||
/// Will panic if ready_channel wasn't called.
|
||||
pub fn counterparty_selected_contest_delay(&self) -> u16 { self.get_channel_parameters().counterparty_parameters.as_ref().unwrap().selected_contest_delay }
|
||||
|
||||
/// The contest_delay value specified by us and applied on transactions broadcastable
|
||||
/// by our counterparty, ie the amount of time that they have to wait to recover their funds
|
||||
/// if they broadcast a transaction.
|
||||
/// Will panic if on_accept wasn't called.
|
||||
pub fn holder_selected_contest_delay(&self) -> u16 { self.accepted_channel_data.as_ref().unwrap().holder_selected_contest_delay }
|
||||
/// Will panic if ready_channel wasn't called.
|
||||
pub fn holder_selected_contest_delay(&self) -> u16 { self.get_channel_parameters().holder_selected_contest_delay }
|
||||
|
||||
/// Whether the holder is the initiator
|
||||
/// Will panic if ready_channel wasn't called.
|
||||
pub fn is_outbound(&self) -> bool { self.get_channel_parameters().is_outbound_from_holder }
|
||||
|
||||
/// Funding outpoint
|
||||
/// Will panic if ready_channel wasn't called.
|
||||
pub fn funding_outpoint(&self) -> &OutPoint { self.get_channel_parameters().funding_outpoint.as_ref().unwrap() }
|
||||
|
||||
/// Obtain a ChannelTransactionParameters for this channel, to be used when verifying or
|
||||
/// building transactions.
|
||||
///
|
||||
/// Will panic if ready_channel wasn't called.
|
||||
pub fn get_channel_parameters(&self) -> &ChannelTransactionParameters {
|
||||
self.channel_parameters.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelKeys for InMemoryChannelKeys {
|
||||
|
@ -469,56 +466,50 @@ impl ChannelKeys for InMemoryChannelKeys {
|
|||
fn pubkeys(&self) -> &ChannelPublicKeys { &self.holder_channel_pubkeys }
|
||||
fn key_derivation_params(&self) -> (u64, u64) { self.key_derivation_params }
|
||||
|
||||
fn sign_counterparty_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, feerate_per_kw: u32, commitment_tx: &Transaction, pre_keys: &PreCalculatedTxCreationKeys, htlcs: &[&HTLCOutputInCommitment], secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
|
||||
if commitment_tx.input.len() != 1 { return Err(()); }
|
||||
let keys = pre_keys.trust_key_derivation();
|
||||
fn sign_counterparty_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
|
||||
let trusted_tx = commitment_tx.trust();
|
||||
let keys = trusted_tx.keys();
|
||||
|
||||
let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
|
||||
let accepted_data = self.accepted_channel_data.as_ref().expect("must accept before signing");
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &accepted_data.counterparty_channel_pubkeys.funding_pubkey);
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey);
|
||||
|
||||
let commitment_sighash = hash_to_message!(&bip143::SigHashCache::new(commitment_tx).signature_hash(0, &channel_funding_redeemscript, self.channel_value_satoshis, SigHashType::All)[..]);
|
||||
let commitment_sig = secp_ctx.sign(&commitment_sighash, &self.funding_key);
|
||||
let built_tx = trusted_tx.built_transaction();
|
||||
let commitment_sig = built_tx.sign(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx);
|
||||
let commitment_txid = built_tx.txid;
|
||||
|
||||
let commitment_txid = commitment_tx.txid();
|
||||
|
||||
let mut htlc_sigs = Vec::with_capacity(htlcs.len());
|
||||
for ref htlc in htlcs {
|
||||
if let Some(_) = htlc.transaction_output_index {
|
||||
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, feerate_per_kw, accepted_data.holder_selected_contest_delay, htlc, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
|
||||
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &keys);
|
||||
let htlc_sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, htlc.amount_msat / 1000, SigHashType::All)[..]);
|
||||
let our_htlc_key = match chan_utils::derive_private_key(&secp_ctx, &keys.per_commitment_point, &self.htlc_base_key) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
htlc_sigs.push(secp_ctx.sign(&htlc_sighash, &our_htlc_key));
|
||||
}
|
||||
let mut htlc_sigs = Vec::with_capacity(commitment_tx.htlcs().len());
|
||||
for htlc in commitment_tx.htlcs() {
|
||||
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_tx.feerate_per_kw(), self.holder_selected_contest_delay(), htlc, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
|
||||
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &keys);
|
||||
let htlc_sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, htlc.amount_msat / 1000, SigHashType::All)[..]);
|
||||
let holder_htlc_key = match chan_utils::derive_private_key(&secp_ctx, &keys.per_commitment_point, &self.htlc_base_key) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
htlc_sigs.push(secp_ctx.sign(&htlc_sighash, &holder_htlc_key));
|
||||
}
|
||||
|
||||
Ok((commitment_sig, htlc_sigs))
|
||||
}
|
||||
|
||||
fn sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
fn sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
|
||||
let counterparty_channel_data = self.accepted_channel_data.as_ref().expect("must accept before signing");
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_channel_data.counterparty_channel_pubkeys.funding_pubkey);
|
||||
|
||||
Ok(holder_commitment_tx.get_holder_sig(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx))
|
||||
let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey);
|
||||
let sig = commitment_tx.trust().built_transaction().sign(&self.funding_key, &funding_redeemscript, self.channel_value_satoshis, secp_ctx);
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
|
||||
fn unsafe_sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
fn unsafe_sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
|
||||
let counterparty_channel_pubkeys = &self.accepted_channel_data.as_ref().expect("must accept before signing").counterparty_channel_pubkeys;
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_channel_pubkeys.funding_pubkey);
|
||||
|
||||
Ok(holder_commitment_tx.get_holder_sig(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx))
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey);
|
||||
Ok(commitment_tx.trust().built_transaction().sign(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx))
|
||||
}
|
||||
|
||||
fn sign_holder_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()> {
|
||||
let counterparty_selected_contest_delay = self.accepted_channel_data.as_ref().unwrap().counterparty_selected_contest_delay;
|
||||
holder_commitment_tx.get_htlc_sigs(&self.htlc_base_key, counterparty_selected_contest_delay, secp_ctx)
|
||||
fn sign_holder_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Vec<Signature>, ()> {
|
||||
let channel_parameters = self.get_channel_parameters();
|
||||
let trusted_tx = commitment_tx.trust();
|
||||
trusted_tx.get_htlc_sigs(&self.htlc_base_key, &channel_parameters.as_holder_broadcastable(), secp_ctx)
|
||||
}
|
||||
|
||||
fn sign_justice_transaction<T: secp256k1::Signing + secp256k1::Verification>(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &Option<HTLCOutputInCommitment>, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
|
@ -575,8 +566,7 @@ impl ChannelKeys for InMemoryChannelKeys {
|
|||
if closing_tx.output.len() > 2 { return Err(()); }
|
||||
|
||||
let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
|
||||
let counterparty_channel_data = self.accepted_channel_data.as_ref().expect("must accept before signing");
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_channel_data.counterparty_channel_pubkeys.funding_pubkey);
|
||||
let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey);
|
||||
|
||||
let sighash = hash_to_message!(&bip143::SigHashCache::new(closing_tx)
|
||||
.signature_hash(0, &channel_funding_redeemscript, self.channel_value_satoshis, SigHashType::All)[..]);
|
||||
|
@ -588,19 +578,13 @@ impl ChannelKeys for InMemoryChannelKeys {
|
|||
Ok(secp_ctx.sign(&msghash, &self.funding_key))
|
||||
}
|
||||
|
||||
fn on_accept(&mut self, channel_pubkeys: &ChannelPublicKeys, counterparty_selected_contest_delay: u16, holder_selected_contest_delay: u16) {
|
||||
assert!(self.accepted_channel_data.is_none(), "Already accepted");
|
||||
self.accepted_channel_data = Some(AcceptedChannelData {
|
||||
counterparty_channel_pubkeys: channel_pubkeys.clone(),
|
||||
counterparty_selected_contest_delay,
|
||||
holder_selected_contest_delay,
|
||||
});
|
||||
fn ready_channel(&mut self, channel_parameters: &ChannelTransactionParameters) {
|
||||
assert!(self.channel_parameters.is_none(), "Acceptance already noted");
|
||||
assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated");
|
||||
self.channel_parameters = Some(channel_parameters.clone());
|
||||
}
|
||||
}
|
||||
|
||||
impl_writeable!(AcceptedChannelData, 0,
|
||||
{ counterparty_channel_pubkeys, counterparty_selected_contest_delay, holder_selected_contest_delay });
|
||||
|
||||
impl Writeable for InMemoryChannelKeys {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.funding_key.write(writer)?;
|
||||
|
@ -609,7 +593,7 @@ impl Writeable for InMemoryChannelKeys {
|
|||
self.delayed_payment_base_key.write(writer)?;
|
||||
self.htlc_base_key.write(writer)?;
|
||||
self.commitment_seed.write(writer)?;
|
||||
self.accepted_channel_data.write(writer)?;
|
||||
self.channel_parameters.write(writer)?;
|
||||
self.channel_value_satoshis.write(writer)?;
|
||||
self.key_derivation_params.0.write(writer)?;
|
||||
self.key_derivation_params.1.write(writer)?;
|
||||
|
@ -645,7 +629,7 @@ impl Readable for InMemoryChannelKeys {
|
|||
commitment_seed,
|
||||
channel_value_satoshis,
|
||||
holder_channel_pubkeys,
|
||||
accepted_channel_data: counterparty_channel_data,
|
||||
channel_parameters: counterparty_channel_data,
|
||||
key_derivation_params: (params_1, params_2),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
use bitcoin::blockdata::script::{Script,Builder};
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::blockdata::transaction::{TxIn,TxOut,OutPoint,Transaction, SigHashType};
|
||||
use bitcoin::consensus::encode::{Decodable, Encodable};
|
||||
use bitcoin::consensus::encode;
|
||||
use bitcoin::util::bip143;
|
||||
|
||||
use bitcoin::hashes::{Hash, HashEngine};
|
||||
|
@ -25,17 +23,29 @@ use bitcoin::hash_types::{Txid, PubkeyHash};
|
|||
|
||||
use ln::channelmanager::{PaymentHash, PaymentPreimage};
|
||||
use ln::msgs::DecodeError;
|
||||
use util::ser::{Readable, Writeable, Writer, WriterWriteAdaptor};
|
||||
use util::ser::{Readable, Writeable, Writer, MAX_BUF_SIZE};
|
||||
use util::byte_utils;
|
||||
|
||||
use bitcoin::hash_types::WPubkeyHash;
|
||||
use bitcoin::secp256k1::key::{SecretKey, PublicKey};
|
||||
use bitcoin::secp256k1::{Secp256k1, Signature};
|
||||
use bitcoin::secp256k1::{Secp256k1, Signature, Message};
|
||||
use bitcoin::secp256k1::Error as SecpError;
|
||||
use bitcoin::secp256k1;
|
||||
|
||||
use std::{cmp, mem};
|
||||
use std::cmp;
|
||||
use ln::chan_utils;
|
||||
use util::transaction_utils::sort_outputs;
|
||||
use ln::channel::INITIAL_COMMITMENT_NUMBER;
|
||||
use std::io::Read;
|
||||
use std::ops::Deref;
|
||||
use chain;
|
||||
|
||||
const MAX_ALLOC_SIZE: usize = 64*1024;
|
||||
const HTLC_OUTPUT_IN_COMMITMENT_SIZE: usize = 1 + 8 + 4 + 32 + 5;
|
||||
|
||||
pub(crate) const MAX_HTLCS: u16 = 483;
|
||||
|
||||
// This checks that the buffer size is greater than the maximum possible size for serialized HTLCS
|
||||
const _EXCESS_BUFFER_SIZE: usize = MAX_BUF_SIZE - MAX_HTLCS as usize * HTLC_OUTPUT_IN_COMMITMENT_SIZE;
|
||||
|
||||
pub(super) const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
|
||||
pub(super) const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
|
||||
|
@ -294,7 +304,7 @@ pub fn derive_public_revocation_key<T: secp256k1::Verification>(secp_ctx: &Secp2
|
|||
///
|
||||
/// These keys are assumed to be good, either because the code derived them from
|
||||
/// channel basepoints via the new function, or they were obtained via
|
||||
/// PreCalculatedTxCreationKeys.trust_key_derivation because we trusted the source of the
|
||||
/// CommitmentTransaction.trust().keys() because we trusted the source of the
|
||||
/// pre-calculated keys.
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct TxCreationKeys {
|
||||
|
@ -314,31 +324,6 @@ pub struct TxCreationKeys {
|
|||
impl_writeable!(TxCreationKeys, 33*6,
|
||||
{ per_commitment_point, revocation_key, broadcaster_htlc_key, countersignatory_htlc_key, broadcaster_delayed_payment_key });
|
||||
|
||||
/// The per-commitment point and a set of pre-calculated public keys used for transaction creation
|
||||
/// in the signer.
|
||||
/// The pre-calculated keys are an optimization, because ChannelKeys has enough
|
||||
/// information to re-derive them.
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct PreCalculatedTxCreationKeys(TxCreationKeys);
|
||||
|
||||
impl PreCalculatedTxCreationKeys {
|
||||
/// Create a new PreCalculatedTxCreationKeys from TxCreationKeys
|
||||
pub fn new(keys: TxCreationKeys) -> Self {
|
||||
PreCalculatedTxCreationKeys(keys)
|
||||
}
|
||||
|
||||
/// The pre-calculated transaction creation public keys.
|
||||
/// An external validating signer should not trust these keys.
|
||||
pub fn trust_key_derivation(&self) -> &TxCreationKeys {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// The transaction per-commitment point
|
||||
pub fn per_commitment_point(&self) -> &PublicKey {
|
||||
&self.0.per_commitment_point
|
||||
}
|
||||
}
|
||||
|
||||
/// One counterparty's public keys which do not change over the life of a channel.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ChannelPublicKeys {
|
||||
|
@ -373,7 +358,8 @@ impl_writeable!(ChannelPublicKeys, 33*5, {
|
|||
|
||||
|
||||
impl TxCreationKeys {
|
||||
/// Create a new TxCreationKeys from channel base points and the per-commitment point
|
||||
/// Create per-state keys from channel base points and the per-commitment point.
|
||||
/// Key set is asymmetric and can't be used as part of counter-signatory set of transactions.
|
||||
pub fn derive_new<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, broadcaster_delayed_payment_base: &PublicKey, broadcaster_htlc_base: &PublicKey, countersignatory_revocation_base: &PublicKey, countersignatory_htlc_base: &PublicKey) -> Result<TxCreationKeys, SecpError> {
|
||||
Ok(TxCreationKeys {
|
||||
per_commitment_point: per_commitment_point.clone(),
|
||||
|
@ -383,6 +369,19 @@ impl TxCreationKeys {
|
|||
broadcaster_delayed_payment_key: derive_public_key(&secp_ctx, &per_commitment_point, &broadcaster_delayed_payment_base)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate per-state keys from channel static keys.
|
||||
/// Key set is asymmetric and can't be used as part of counter-signatory set of transactions.
|
||||
pub fn from_channel_static_keys<T: secp256k1::Signing + secp256k1::Verification>(per_commitment_point: &PublicKey, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>) -> Result<TxCreationKeys, SecpError> {
|
||||
TxCreationKeys::derive_new(
|
||||
&secp_ctx,
|
||||
&per_commitment_point,
|
||||
&broadcaster_keys.delayed_payment_basepoint,
|
||||
&broadcaster_keys.htlc_basepoint,
|
||||
&countersignatory_keys.revocation_basepoint,
|
||||
&countersignatory_keys.htlc_basepoint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A script either spendable by the revocation
|
||||
|
@ -422,7 +421,7 @@ pub struct HTLCOutputInCommitment {
|
|||
pub transaction_output_index: Option<u32>,
|
||||
}
|
||||
|
||||
impl_writeable!(HTLCOutputInCommitment, 1 + 8 + 4 + 32 + 5, {
|
||||
impl_writeable!(HTLCOutputInCommitment, HTLC_OUTPUT_IN_COMMITMENT_SIZE, {
|
||||
offered,
|
||||
amount_msat,
|
||||
cltv_expiry,
|
||||
|
@ -551,130 +550,216 @@ pub fn build_htlc_transaction(prev_hash: &Txid, feerate_per_kw: u32, contest_del
|
|||
}
|
||||
}
|
||||
|
||||
/// Per-channel data used to build transactions in conjunction with the per-commitment data (CommitmentTransaction).
|
||||
/// The fields are organized by holder/counterparty.
|
||||
///
|
||||
/// Normally, this is converted to the broadcaster/countersignatory-organized DirectedChannelTransactionParameters
|
||||
/// before use, via the as_holder_broadcastable and as_counterparty_broadcastable functions.
|
||||
#[derive(Clone)]
|
||||
/// We use this to track holder commitment transactions and put off signing them until we are ready
|
||||
/// to broadcast. This class can be used inside a signer implementation to generate a signature
|
||||
/// given the relevant secret key.
|
||||
pub struct HolderCommitmentTransaction {
|
||||
// TODO: We should migrate away from providing the transaction, instead providing enough to
|
||||
// allow the ChannelKeys to construct it from scratch. Luckily we already have HTLC data here,
|
||||
// so we're probably most of the way there.
|
||||
/// The commitment transaction itself, in unsigned form.
|
||||
pub unsigned_tx: Transaction,
|
||||
/// Our counterparty's signature for the transaction, above.
|
||||
pub counterparty_sig: Signature,
|
||||
// Which order the signatures should go in when constructing the final commitment tx witness.
|
||||
// The user should be able to reconstruc this themselves, so we don't bother to expose it.
|
||||
holder_sig_first: bool,
|
||||
pub(crate) keys: TxCreationKeys,
|
||||
/// The feerate paid per 1000-weight-unit in this commitment transaction. This value is
|
||||
/// controlled by the channel initiator.
|
||||
pub feerate_per_kw: u32,
|
||||
/// The HTLCs and counterparty htlc signatures which were included in this commitment transaction.
|
||||
///
|
||||
/// Note that this includes all HTLCs, including ones which were considered dust and not
|
||||
/// actually included in the transaction as it appears on-chain, but who's value is burned as
|
||||
/// fees and not included in the to_holder or to_counterparty outputs.
|
||||
///
|
||||
/// The counterparty HTLC signatures in the second element will always be set for non-dust HTLCs, ie
|
||||
/// those for which transaction_output_index.is_some().
|
||||
pub per_htlc: Vec<(HTLCOutputInCommitment, Option<Signature>)>,
|
||||
pub struct ChannelTransactionParameters {
|
||||
/// Holder public keys
|
||||
pub holder_pubkeys: ChannelPublicKeys,
|
||||
/// The contest delay selected by the holder, which applies to counterparty-broadcast transactions
|
||||
pub holder_selected_contest_delay: u16,
|
||||
/// Whether the holder is the initiator of this channel.
|
||||
/// This is an input to the commitment number obscure factor computation.
|
||||
pub is_outbound_from_holder: bool,
|
||||
/// The late-bound counterparty channel transaction parameters.
|
||||
/// These parameters are populated at the point in the protocol where the counterparty provides them.
|
||||
pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
|
||||
/// The late-bound funding outpoint
|
||||
pub funding_outpoint: Option<chain::transaction::OutPoint>,
|
||||
}
|
||||
|
||||
/// Late-bound per-channel counterparty data used to build transactions.
|
||||
#[derive(Clone)]
|
||||
pub struct CounterpartyChannelTransactionParameters {
|
||||
/// Counter-party public keys
|
||||
pub pubkeys: ChannelPublicKeys,
|
||||
/// The contest delay selected by the counterparty, which applies to holder-broadcast transactions
|
||||
pub selected_contest_delay: u16,
|
||||
}
|
||||
|
||||
impl ChannelTransactionParameters {
|
||||
/// Whether the late bound parameters are populated.
|
||||
pub fn is_populated(&self) -> bool {
|
||||
self.counterparty_parameters.is_some() && self.funding_outpoint.is_some()
|
||||
}
|
||||
|
||||
/// Convert the holder/counterparty parameters to broadcaster/countersignatory-organized parameters,
|
||||
/// given that the holder is the broadcaster.
|
||||
///
|
||||
/// self.is_populated() must be true before calling this function.
|
||||
pub fn as_holder_broadcastable(&self) -> DirectedChannelTransactionParameters {
|
||||
assert!(self.is_populated(), "self.late_parameters must be set before using as_holder_broadcastable");
|
||||
DirectedChannelTransactionParameters {
|
||||
inner: self,
|
||||
holder_is_broadcaster: true
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the holder/counterparty parameters to broadcaster/countersignatory-organized parameters,
|
||||
/// given that the counterparty is the broadcaster.
|
||||
///
|
||||
/// self.is_populated() must be true before calling this function.
|
||||
pub fn as_counterparty_broadcastable(&self) -> DirectedChannelTransactionParameters {
|
||||
assert!(self.is_populated(), "self.late_parameters must be set before using as_counterparty_broadcastable");
|
||||
DirectedChannelTransactionParameters {
|
||||
inner: self,
|
||||
holder_is_broadcaster: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_writeable!(CounterpartyChannelTransactionParameters, 0, {
|
||||
pubkeys,
|
||||
selected_contest_delay
|
||||
});
|
||||
|
||||
impl_writeable!(ChannelTransactionParameters, 0, {
|
||||
holder_pubkeys,
|
||||
holder_selected_contest_delay,
|
||||
is_outbound_from_holder,
|
||||
counterparty_parameters,
|
||||
funding_outpoint
|
||||
});
|
||||
|
||||
/// Static channel fields used to build transactions given per-commitment fields, organized by
|
||||
/// broadcaster/countersignatory.
|
||||
///
|
||||
/// This is derived from the holder/counterparty-organized ChannelTransactionParameters via the
|
||||
/// as_holder_broadcastable and as_counterparty_broadcastable functions.
|
||||
pub struct DirectedChannelTransactionParameters<'a> {
|
||||
/// The holder's channel static parameters
|
||||
inner: &'a ChannelTransactionParameters,
|
||||
/// Whether the holder is the broadcaster
|
||||
holder_is_broadcaster: bool,
|
||||
}
|
||||
|
||||
impl<'a> DirectedChannelTransactionParameters<'a> {
|
||||
/// Get the channel pubkeys for the broadcaster
|
||||
pub fn broadcaster_pubkeys(&self) -> &ChannelPublicKeys {
|
||||
if self.holder_is_broadcaster {
|
||||
&self.inner.holder_pubkeys
|
||||
} else {
|
||||
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the channel pubkeys for the countersignatory
|
||||
pub fn countersignatory_pubkeys(&self) -> &ChannelPublicKeys {
|
||||
if self.holder_is_broadcaster {
|
||||
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
|
||||
} else {
|
||||
&self.inner.holder_pubkeys
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the contest delay applicable to the transactions.
|
||||
/// Note that the contest delay was selected by the countersignatory.
|
||||
pub fn contest_delay(&self) -> u16 {
|
||||
let counterparty_parameters = self.inner.counterparty_parameters.as_ref().unwrap();
|
||||
if self.holder_is_broadcaster { counterparty_parameters.selected_contest_delay } else { self.inner.holder_selected_contest_delay }
|
||||
}
|
||||
|
||||
/// Whether the channel is outbound from the broadcaster.
|
||||
///
|
||||
/// The boolean representing the side that initiated the channel is
|
||||
/// an input to the commitment number obscure factor computation.
|
||||
pub fn is_outbound(&self) -> bool {
|
||||
if self.holder_is_broadcaster { self.inner.is_outbound_from_holder } else { !self.inner.is_outbound_from_holder }
|
||||
}
|
||||
|
||||
/// The funding outpoint
|
||||
pub fn funding_outpoint(&self) -> OutPoint {
|
||||
self.inner.funding_outpoint.unwrap().into_bitcoin_outpoint()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information needed to build and sign a holder's commitment transaction.
|
||||
///
|
||||
/// The transaction is only signed once we are ready to broadcast.
|
||||
#[derive(Clone)]
|
||||
pub struct HolderCommitmentTransaction {
|
||||
inner: CommitmentTransaction,
|
||||
/// Our counterparty's signature for the transaction
|
||||
pub counterparty_sig: Signature,
|
||||
/// All non-dust counterparty HTLC signatures, in the order they appear in the transaction
|
||||
pub counterparty_htlc_sigs: Vec<Signature>,
|
||||
// Which order the signatures should go in when constructing the final commitment tx witness.
|
||||
// The user should be able to reconstruct this themselves, so we don't bother to expose it.
|
||||
holder_sig_first: bool,
|
||||
}
|
||||
|
||||
impl Deref for HolderCommitmentTransaction {
|
||||
type Target = CommitmentTransaction;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl PartialEq for HolderCommitmentTransaction {
|
||||
// We dont care whether we are signed in equality comparison
|
||||
fn eq(&self, o: &Self) -> bool {
|
||||
self.inner == o.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl_writeable!(HolderCommitmentTransaction, 0, {
|
||||
inner, counterparty_sig, counterparty_htlc_sigs, holder_sig_first
|
||||
});
|
||||
|
||||
impl HolderCommitmentTransaction {
|
||||
#[cfg(test)]
|
||||
pub fn dummy() -> Self {
|
||||
let dummy_input = TxIn {
|
||||
previous_output: OutPoint {
|
||||
txid: Default::default(),
|
||||
vout: 0,
|
||||
},
|
||||
script_sig: Default::default(),
|
||||
sequence: 0,
|
||||
witness: vec![]
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
|
||||
let dummy_sig = secp_ctx.sign(&secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
|
||||
|
||||
let keys = TxCreationKeys {
|
||||
per_commitment_point: dummy_key.clone(),
|
||||
revocation_key: dummy_key.clone(),
|
||||
broadcaster_htlc_key: dummy_key.clone(),
|
||||
countersignatory_htlc_key: dummy_key.clone(),
|
||||
broadcaster_delayed_payment_key: dummy_key.clone(),
|
||||
};
|
||||
let dummy_key = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
|
||||
let dummy_sig = Secp256k1::new().sign(&secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
|
||||
Self {
|
||||
unsigned_tx: Transaction {
|
||||
version: 2,
|
||||
input: vec![dummy_input],
|
||||
output: Vec::new(),
|
||||
lock_time: 0,
|
||||
},
|
||||
let channel_pubkeys = ChannelPublicKeys {
|
||||
funding_pubkey: dummy_key.clone(),
|
||||
revocation_basepoint: dummy_key.clone(),
|
||||
payment_point: dummy_key.clone(),
|
||||
delayed_payment_basepoint: dummy_key.clone(),
|
||||
htlc_basepoint: dummy_key.clone()
|
||||
};
|
||||
let channel_parameters = ChannelTransactionParameters {
|
||||
holder_pubkeys: channel_pubkeys.clone(),
|
||||
holder_selected_contest_delay: 0,
|
||||
is_outbound_from_holder: false,
|
||||
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_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();
|
||||
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable());
|
||||
HolderCommitmentTransaction {
|
||||
inner,
|
||||
counterparty_sig: dummy_sig,
|
||||
holder_sig_first: false,
|
||||
keys: TxCreationKeys {
|
||||
per_commitment_point: dummy_key.clone(),
|
||||
revocation_key: dummy_key.clone(),
|
||||
broadcaster_htlc_key: dummy_key.clone(),
|
||||
countersignatory_htlc_key: dummy_key.clone(),
|
||||
broadcaster_delayed_payment_key: dummy_key.clone(),
|
||||
},
|
||||
feerate_per_kw: 0,
|
||||
per_htlc: Vec::new()
|
||||
counterparty_htlc_sigs: Vec::new(),
|
||||
holder_sig_first: false
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a new HolderCommitmentTransaction based on a raw commitment transaction,
|
||||
/// counterparty signature and both parties keys.
|
||||
///
|
||||
/// The unsigned transaction outputs must be consistent with htlc_data. This function
|
||||
/// only checks that the shape and amounts are consistent, but does not check the scriptPubkey.
|
||||
pub fn new_missing_holder_sig(unsigned_tx: Transaction, counterparty_sig: Signature, holder_funding_key: &PublicKey, counterparty_funding_key: &PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlc_data: Vec<(HTLCOutputInCommitment, Option<Signature>)>) -> HolderCommitmentTransaction {
|
||||
if unsigned_tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
|
||||
if unsigned_tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }
|
||||
|
||||
for htlc in &htlc_data {
|
||||
if let Some(index) = htlc.0.transaction_output_index {
|
||||
let out = &unsigned_tx.output[index as usize];
|
||||
if out.value != htlc.0.amount_msat / 1000 {
|
||||
panic!("HTLC at index {} has incorrect amount", index);
|
||||
}
|
||||
if !out.script_pubkey.is_v0_p2wsh() {
|
||||
panic!("HTLC at index {} doesn't have p2wsh scriptPubkey", index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new holder transaction with the given counterparty signatures.
|
||||
/// The funding keys are used to figure out which signature should go first when building the transaction for broadcast.
|
||||
pub fn new(commitment_tx: CommitmentTransaction, counterparty_sig: Signature, counterparty_htlc_sigs: Vec<Signature>, holder_funding_key: &PublicKey, counterparty_funding_key: &PublicKey) -> Self {
|
||||
Self {
|
||||
unsigned_tx,
|
||||
inner: commitment_tx,
|
||||
counterparty_sig,
|
||||
counterparty_htlc_sigs,
|
||||
holder_sig_first: holder_funding_key.serialize()[..] < counterparty_funding_key.serialize()[..],
|
||||
keys,
|
||||
feerate_per_kw,
|
||||
per_htlc: htlc_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// The pre-calculated transaction creation public keys.
|
||||
/// An external validating signer should not trust these keys.
|
||||
pub fn trust_key_derivation(&self) -> &TxCreationKeys {
|
||||
&self.keys
|
||||
}
|
||||
|
||||
/// Get the txid of the holder commitment transaction contained in this
|
||||
/// HolderCommitmentTransaction
|
||||
pub fn txid(&self) -> Txid {
|
||||
self.unsigned_tx.txid()
|
||||
}
|
||||
|
||||
/// Gets holder signature for the contained commitment transaction given holder funding private key.
|
||||
///
|
||||
/// Funding key is your key included in the 2-2 funding_outpoint lock. Should be provided
|
||||
/// by your ChannelKeys.
|
||||
/// Funding redeemscript is script locking funding_outpoint. This is the mutlsig script
|
||||
/// between your own funding key and your counterparty's. Currently, this is provided in
|
||||
/// ChannelKeys::sign_holder_commitment() calls directly.
|
||||
/// Channel value is amount locked in funding_outpoint.
|
||||
pub fn get_holder_sig<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature {
|
||||
let sighash = hash_to_message!(&bip143::SigHashCache::new(&self.unsigned_tx)
|
||||
.signature_hash(0, funding_redeemscript, channel_value_satoshis, SigHashType::All)[..]);
|
||||
secp_ctx.sign(&sighash, funding_key)
|
||||
}
|
||||
|
||||
pub(crate) fn add_holder_sig(&self, funding_redeemscript: &Script, holder_sig: Signature) -> Transaction {
|
||||
let mut tx = self.unsigned_tx.clone();
|
||||
// First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element.
|
||||
let mut tx = self.inner.built.transaction.clone();
|
||||
tx.input[0].witness.push(Vec::new());
|
||||
|
||||
if self.holder_sig_first {
|
||||
|
@ -690,59 +775,402 @@ impl HolderCommitmentTransaction {
|
|||
tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
|
||||
tx
|
||||
}
|
||||
}
|
||||
|
||||
/// A pre-built Bitcoin commitment transaction and its txid.
|
||||
#[derive(Clone)]
|
||||
pub struct BuiltCommitmentTransaction {
|
||||
/// The commitment transaction
|
||||
pub transaction: Transaction,
|
||||
/// The txid for the commitment transaction.
|
||||
///
|
||||
/// This is provided as a performance optimization, instead of calling transaction.txid()
|
||||
/// multiple times.
|
||||
pub txid: Txid,
|
||||
}
|
||||
|
||||
impl_writeable!(BuiltCommitmentTransaction, 0, { transaction, txid });
|
||||
|
||||
impl BuiltCommitmentTransaction {
|
||||
/// Get the SIGHASH_ALL sighash value of the transaction.
|
||||
///
|
||||
/// This can be used to verify a signature.
|
||||
pub fn get_sighash_all(&self, funding_redeemscript: &Script, channel_value_satoshis: u64) -> Message {
|
||||
let sighash = &bip143::SigHashCache::new(&self.transaction).signature_hash(0, funding_redeemscript, channel_value_satoshis, SigHashType::All)[..];
|
||||
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 {
|
||||
let sighash = self.get_sighash_all(funding_redeemscript, channel_value_satoshis);
|
||||
secp_ctx.sign(&sighash, funding_key)
|
||||
}
|
||||
}
|
||||
|
||||
/// This class tracks the per-transaction information needed to build a commitment transaction and to
|
||||
/// actually build it and sign. It is used for holder transactions that we sign only when needed
|
||||
/// and for transactions we sign for the counterparty.
|
||||
///
|
||||
/// This class can be used inside a signer implementation to generate a signature given the relevant
|
||||
/// secret key.
|
||||
#[derive(Clone)]
|
||||
pub struct CommitmentTransaction {
|
||||
commitment_number: u64,
|
||||
to_broadcaster_value_sat: u64,
|
||||
to_countersignatory_value_sat: u64,
|
||||
feerate_per_kw: u32,
|
||||
htlcs: Vec<HTLCOutputInCommitment>,
|
||||
// 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()
|
||||
built: BuiltCommitmentTransaction,
|
||||
}
|
||||
|
||||
impl PartialEq for CommitmentTransaction {
|
||||
fn eq(&self, o: &Self) -> bool {
|
||||
let eq = self.commitment_number == o.commitment_number &&
|
||||
self.to_broadcaster_value_sat == o.to_broadcaster_value_sat &&
|
||||
self.to_countersignatory_value_sat == o.to_countersignatory_value_sat &&
|
||||
self.feerate_per_kw == o.feerate_per_kw &&
|
||||
self.htlcs == o.htlcs &&
|
||||
self.keys == o.keys;
|
||||
if eq {
|
||||
debug_assert_eq!(self.built.transaction, o.built.transaction);
|
||||
debug_assert_eq!(self.built.txid, o.built.txid);
|
||||
}
|
||||
eq
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Vec<HTLCOutputInCommitment> {
|
||||
#[inline]
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
|
||||
(self.len() as u16).write(w)?;
|
||||
for e in self.iter() {
|
||||
e.write(w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for Vec<HTLCOutputInCommitment> {
|
||||
#[inline]
|
||||
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
|
||||
let len: u16 = Readable::read(r)?;
|
||||
let byte_size = (len as usize)
|
||||
.checked_mul(HTLC_OUTPUT_IN_COMMITMENT_SIZE)
|
||||
.ok_or(DecodeError::BadLengthDescriptor)?;
|
||||
if byte_size > MAX_BUF_SIZE {
|
||||
return Err(DecodeError::BadLengthDescriptor);
|
||||
}
|
||||
let mut ret = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len { ret.push(HTLCOutputInCommitment::read(r)?); }
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl_writeable!(CommitmentTransaction, 0, {
|
||||
commitment_number,
|
||||
to_broadcaster_value_sat,
|
||||
to_countersignatory_value_sat,
|
||||
feerate_per_kw,
|
||||
htlcs,
|
||||
keys,
|
||||
built
|
||||
});
|
||||
|
||||
impl CommitmentTransaction {
|
||||
/// Construct an object of the class while assigning transaction output indices to HTLCs.
|
||||
///
|
||||
/// Populates HTLCOutputInCommitment.transaction_output_index in htlcs_with_aux.
|
||||
///
|
||||
/// The generic T allows the caller to match the HTLC output index with auxiliary data.
|
||||
/// This auxiliary data is not stored in this object.
|
||||
///
|
||||
/// Only include HTLCs that are above the dust limit for the channel.
|
||||
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 {
|
||||
// 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 (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
|
||||
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
|
||||
let txid = transaction.txid();
|
||||
CommitmentTransaction {
|
||||
commitment_number,
|
||||
to_broadcaster_value_sat,
|
||||
to_countersignatory_value_sat,
|
||||
feerate_per_kw,
|
||||
htlcs,
|
||||
keys,
|
||||
built: BuiltCommitmentTransaction {
|
||||
transaction,
|
||||
txid
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters) -> 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 transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
|
||||
let txid = transaction.txid();
|
||||
let built_transaction = BuiltCommitmentTransaction {
|
||||
transaction,
|
||||
txid
|
||||
};
|
||||
Ok(built_transaction)
|
||||
}
|
||||
|
||||
fn make_transaction(obscured_commitment_transaction_number: u64, txins: Vec<TxIn>, outputs: Vec<TxOut>) -> Transaction {
|
||||
Transaction {
|
||||
version: 2,
|
||||
lock_time: ((0x20 as u32) << 8 * 3) | ((obscured_commitment_transaction_number & 0xffffffu64) as u32),
|
||||
input: txins,
|
||||
output: outputs,
|
||||
}
|
||||
}
|
||||
|
||||
// This is used in two cases:
|
||||
// - 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>), ()> {
|
||||
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 = script_for_p2wpkh(&countersignatory_pubkeys.payment_point);
|
||||
txouts.push((
|
||||
TxOut {
|
||||
script_pubkey: script.clone(),
|
||||
value: to_countersignatory_value_sat,
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
if to_broadcaster_value_sat > 0 {
|
||||
let redeem_script = get_revokeable_redeemscript(
|
||||
&keys.revocation_key,
|
||||
contest_delay,
|
||||
&keys.broadcaster_delayed_payment_key,
|
||||
);
|
||||
txouts.push((
|
||||
TxOut {
|
||||
script_pubkey: redeem_script.to_v0_p2wsh(),
|
||||
value: to_broadcaster_value_sat,
|
||||
},
|
||||
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);
|
||||
let txout = TxOut {
|
||||
script_pubkey: script.to_v0_p2wsh(),
|
||||
value: htlc.amount_msat / 1000,
|
||||
};
|
||||
txouts.push((txout, Some(htlc)));
|
||||
}
|
||||
|
||||
// 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 fuzztarget 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 }
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
outputs.push(out.0);
|
||||
}
|
||||
Ok((outputs, htlcs))
|
||||
}
|
||||
|
||||
fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec<TxIn>) {
|
||||
let broadcaster_pubkeys = channel_parameters.broadcaster_pubkeys();
|
||||
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
|
||||
let commitment_transaction_number_obscure_factor = get_commitment_transaction_number_obscure_factor(
|
||||
&broadcaster_pubkeys.payment_point,
|
||||
&countersignatory_pubkeys.payment_point,
|
||||
channel_parameters.is_outbound(),
|
||||
);
|
||||
|
||||
let obscured_commitment_transaction_number =
|
||||
commitment_transaction_number_obscure_factor ^ (INITIAL_COMMITMENT_NUMBER - commitment_number);
|
||||
|
||||
let txins = {
|
||||
let mut ins: Vec<TxIn> = Vec::new();
|
||||
ins.push(TxIn {
|
||||
previous_output: channel_parameters.funding_outpoint(),
|
||||
script_sig: Script::new(),
|
||||
sequence: ((0x80 as u32) << 8 * 3)
|
||||
| ((obscured_commitment_transaction_number >> 3 * 8) as u32),
|
||||
witness: Vec::new(),
|
||||
});
|
||||
ins
|
||||
};
|
||||
(obscured_commitment_transaction_number, txins)
|
||||
}
|
||||
|
||||
/// The backwards-counting commitment number
|
||||
pub fn commitment_number(&self) -> u64 {
|
||||
self.commitment_number
|
||||
}
|
||||
|
||||
/// The value to be sent to the broadcaster
|
||||
pub fn to_broadcaster_value_sat(&self) -> u64 {
|
||||
self.to_broadcaster_value_sat
|
||||
}
|
||||
|
||||
/// The value to be sent to the counterparty
|
||||
pub fn to_countersignatory_value_sat(&self) -> u64 {
|
||||
self.to_countersignatory_value_sat
|
||||
}
|
||||
|
||||
/// The feerate paid per 1000-weight-unit in this commitment transaction.
|
||||
pub fn feerate_per_kw(&self) -> u32 {
|
||||
self.feerate_per_kw
|
||||
}
|
||||
|
||||
/// The non-dust HTLCs (direction, amt, height expiration, hash, transaction output index)
|
||||
/// which were included in this commitment transaction in output order.
|
||||
/// The transaction index is always populated.
|
||||
pub fn htlcs(&self) -> &Vec<HTLCOutputInCommitment> {
|
||||
&self.htlcs
|
||||
}
|
||||
|
||||
/// Trust our pre-built transaction and derived transaction creation public keys.
|
||||
///
|
||||
/// Applies a wrapper which allows access to these fields.
|
||||
///
|
||||
/// This should only be used if you fully trust the builder of this object. It should not
|
||||
/// be used by an external signer - instead use the verify function.
|
||||
pub fn trust(&self) -> TrustedCommitmentTransaction {
|
||||
TrustedCommitmentTransaction { inner: self }
|
||||
}
|
||||
|
||||
/// Verify our pre-built transaction and derived transaction creation public keys.
|
||||
///
|
||||
/// Applies a wrapper which allows access to these fields.
|
||||
///
|
||||
/// An external validating signer must call this method before signing
|
||||
/// or using the built transaction.
|
||||
pub fn verify<T: secp256k1::Signing + secp256k1::Verification>(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>) -> Result<TrustedCommitmentTransaction, ()> {
|
||||
// This is the only field of the key cache that we trust
|
||||
let per_commitment_point = self.keys.per_commitment_point;
|
||||
let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, broadcaster_keys, countersignatory_keys, secp_ctx).unwrap();
|
||||
if keys != self.keys {
|
||||
return Err(());
|
||||
}
|
||||
let tx = self.internal_rebuild_transaction(&keys, channel_parameters)?;
|
||||
if self.built.transaction != tx.transaction || self.built.txid != tx.txid {
|
||||
return Err(());
|
||||
}
|
||||
Ok(TrustedCommitmentTransaction { inner: self })
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper on CommitmentTransaction indicating that the derived fields (the built bitcoin
|
||||
/// transaction and the transaction creation keys) are trusted.
|
||||
///
|
||||
/// See trust() and verify() functions on CommitmentTransaction.
|
||||
///
|
||||
/// This structure implements Deref.
|
||||
pub struct TrustedCommitmentTransaction<'a> {
|
||||
inner: &'a CommitmentTransaction,
|
||||
}
|
||||
|
||||
impl<'a> Deref for TrustedCommitmentTransaction<'a> {
|
||||
type Target = CommitmentTransaction;
|
||||
|
||||
fn deref(&self) -> &Self::Target { self.inner }
|
||||
}
|
||||
|
||||
impl<'a> TrustedCommitmentTransaction<'a> {
|
||||
/// The transaction ID of the built Bitcoin transaction
|
||||
pub fn txid(&self) -> Txid {
|
||||
self.inner.built.txid
|
||||
}
|
||||
|
||||
/// The pre-built Bitcoin commitment transaction
|
||||
pub fn built_transaction(&self) -> &BuiltCommitmentTransaction {
|
||||
&self.inner.built
|
||||
}
|
||||
|
||||
/// The pre-calculated transaction creation public keys.
|
||||
pub fn keys(&self) -> &TxCreationKeys {
|
||||
&self.inner.keys
|
||||
}
|
||||
|
||||
/// Get a signature for each HTLC which was included in the commitment transaction (ie for
|
||||
/// which HTLCOutputInCommitment::transaction_output_index.is_some()).
|
||||
///
|
||||
/// The returned Vec has one entry for each HTLC, and in the same order. For HTLCs which were
|
||||
/// considered dust and not included, a None entry exists, for all others a signature is
|
||||
/// included.
|
||||
pub fn get_htlc_sigs<T: secp256k1::Signing + secp256k1::Verification>(&self, htlc_base_key: &SecretKey, counterparty_selected_contest_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()> {
|
||||
let txid = self.txid();
|
||||
let mut ret = Vec::with_capacity(self.per_htlc.len());
|
||||
let holder_htlc_key = derive_private_key(secp_ctx, &self.keys.per_commitment_point, htlc_base_key).map_err(|_| ())?;
|
||||
/// The returned Vec has one entry for each HTLC, and in the same order.
|
||||
pub fn get_htlc_sigs<T: secp256k1::Signing>(&self, htlc_base_key: &SecretKey, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1<T>) -> Result<Vec<Signature>, ()> {
|
||||
let inner = self.inner;
|
||||
let keys = &inner.keys;
|
||||
let txid = inner.built.txid;
|
||||
let mut ret = Vec::with_capacity(inner.htlcs.len());
|
||||
let holder_htlc_key = derive_private_key(secp_ctx, &inner.keys.per_commitment_point, htlc_base_key).map_err(|_| ())?;
|
||||
|
||||
for this_htlc in self.per_htlc.iter() {
|
||||
if this_htlc.0.transaction_output_index.is_some() {
|
||||
let htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, counterparty_selected_contest_delay, &this_htlc.0, &self.keys.broadcaster_delayed_payment_key, &self.keys.revocation_key);
|
||||
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, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
|
||||
|
||||
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.keys.broadcaster_htlc_key, &self.keys.countersignatory_htlc_key, &self.keys.revocation_key);
|
||||
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
|
||||
|
||||
let sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, this_htlc.0.amount_msat / 1000, SigHashType::All)[..]);
|
||||
ret.push(Some(secp_ctx.sign(&sighash, &holder_htlc_key)));
|
||||
} else {
|
||||
ret.push(None);
|
||||
}
|
||||
let sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, this_htlc.amount_msat / 1000, SigHashType::All)[..]);
|
||||
ret.push(secp_ctx.sign(&sighash, &holder_htlc_key));
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC transaction signature.
|
||||
pub(crate) fn get_signed_htlc_tx(&self, htlc_index: usize, signature: &Signature, preimage: &Option<PaymentPreimage>, counterparty_selected_contest_delay: u16) -> Transaction {
|
||||
let txid = self.txid();
|
||||
let this_htlc = &self.per_htlc[htlc_index];
|
||||
assert!(this_htlc.0.transaction_output_index.is_some());
|
||||
pub(crate) fn get_signed_htlc_tx(&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>) -> Transaction {
|
||||
let inner = self.inner;
|
||||
let keys = &inner.keys;
|
||||
let txid = inner.built.txid;
|
||||
let this_htlc = &inner.htlcs[htlc_index];
|
||||
assert!(this_htlc.transaction_output_index.is_some());
|
||||
// if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction.
|
||||
if !this_htlc.0.offered && preimage.is_none() { unreachable!(); }
|
||||
if !this_htlc.offered && preimage.is_none() { unreachable!(); }
|
||||
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
|
||||
if this_htlc.0.offered && preimage.is_some() { unreachable!(); }
|
||||
if this_htlc.offered && preimage.is_some() { unreachable!(); }
|
||||
|
||||
let mut htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, counterparty_selected_contest_delay, &this_htlc.0, &self.keys.broadcaster_delayed_payment_key, &self.keys.revocation_key);
|
||||
// Channel should have checked that we have a counterparty signature for this HTLC at
|
||||
// creation, and we should have a sensible htlc transaction:
|
||||
assert!(this_htlc.1.is_some());
|
||||
let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
|
||||
|
||||
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.keys.broadcaster_htlc_key, &self.keys.countersignatory_htlc_key, &self.keys.revocation_key);
|
||||
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
|
||||
|
||||
// First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element.
|
||||
htlc_tx.input[0].witness.push(Vec::new());
|
||||
|
||||
htlc_tx.input[0].witness.push(this_htlc.1.unwrap().serialize_der().to_vec());
|
||||
htlc_tx.input[0].witness.push(counterparty_signature.serialize_der().to_vec());
|
||||
htlc_tx.input[0].witness.push(signature.serialize_der().to_vec());
|
||||
htlc_tx.input[0].witness[1].push(SigHashType::All as u8);
|
||||
htlc_tx.input[0].witness[2].push(SigHashType::All as u8);
|
||||
|
||||
if this_htlc.0.offered {
|
||||
if this_htlc.offered {
|
||||
// Due to BIP146 (MINIMALIF) this must be a zero-length element to relay.
|
||||
htlc_tx.input[0].witness.push(Vec::new());
|
||||
} else {
|
||||
|
@ -753,66 +1181,36 @@ impl HolderCommitmentTransaction {
|
|||
htlc_tx
|
||||
}
|
||||
}
|
||||
impl PartialEq for HolderCommitmentTransaction {
|
||||
// We dont care whether we are signed in equality comparison
|
||||
fn eq(&self, o: &Self) -> bool {
|
||||
self.txid() == o.txid()
|
||||
}
|
||||
}
|
||||
impl Writeable for HolderCommitmentTransaction {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
|
||||
if let Err(e) = self.unsigned_tx.consensus_encode(&mut WriterWriteAdaptor(writer)) {
|
||||
match e {
|
||||
encode::Error::Io(e) => return Err(e),
|
||||
_ => panic!("holder tx must have been well-formed!"),
|
||||
}
|
||||
}
|
||||
self.counterparty_sig.write(writer)?;
|
||||
self.holder_sig_first.write(writer)?;
|
||||
self.keys.write(writer)?;
|
||||
self.feerate_per_kw.write(writer)?;
|
||||
writer.write_all(&byte_utils::be64_to_array(self.per_htlc.len() as u64))?;
|
||||
for &(ref htlc, ref sig) in self.per_htlc.iter() {
|
||||
htlc.write(writer)?;
|
||||
sig.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Readable for HolderCommitmentTransaction {
|
||||
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
|
||||
let unsigned_tx = match Transaction::consensus_decode(reader.by_ref()) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => match e {
|
||||
encode::Error::Io(ioe) => return Err(DecodeError::Io(ioe)),
|
||||
_ => return Err(DecodeError::InvalidValue),
|
||||
},
|
||||
};
|
||||
let counterparty_sig = Readable::read(reader)?;
|
||||
let holder_sig_first = Readable::read(reader)?;
|
||||
let keys = Readable::read(reader)?;
|
||||
let feerate_per_kw = Readable::read(reader)?;
|
||||
let htlcs_count: u64 = Readable::read(reader)?;
|
||||
let mut per_htlc = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / mem::size_of::<(HTLCOutputInCommitment, Option<Signature>)>()));
|
||||
for _ in 0..htlcs_count {
|
||||
let htlc: HTLCOutputInCommitment = Readable::read(reader)?;
|
||||
let sigs = Readable::read(reader)?;
|
||||
per_htlc.push((htlc, sigs));
|
||||
}
|
||||
|
||||
if unsigned_tx.input.len() != 1 {
|
||||
// Ensure tx didn't hit the 0-input ambiguity case.
|
||||
return Err(DecodeError::InvalidValue);
|
||||
}
|
||||
Ok(Self {
|
||||
unsigned_tx,
|
||||
counterparty_sig,
|
||||
holder_sig_first,
|
||||
keys,
|
||||
feerate_per_kw,
|
||||
per_htlc,
|
||||
})
|
||||
/// Get the transaction number obscure factor
|
||||
pub fn get_commitment_transaction_number_obscure_factor(
|
||||
broadcaster_payment_basepoint: &PublicKey,
|
||||
countersignatory_payment_basepoint: &PublicKey,
|
||||
outbound_from_broadcaster: bool,
|
||||
) -> u64 {
|
||||
let mut sha = Sha256::engine();
|
||||
|
||||
if outbound_from_broadcaster {
|
||||
sha.input(&broadcaster_payment_basepoint.serialize());
|
||||
sha.input(&countersignatory_payment_basepoint.serialize());
|
||||
} else {
|
||||
sha.input(&countersignatory_payment_basepoint.serialize());
|
||||
sha.input(&broadcaster_payment_basepoint.serialize());
|
||||
}
|
||||
let res = Sha256::from_engine(sha).into_inner();
|
||||
|
||||
((res[26] as u64) << 5 * 8)
|
||||
| ((res[27] as u64) << 4 * 8)
|
||||
| ((res[28] as u64) << 3 * 8)
|
||||
| ((res[29] as u64) << 2 * 8)
|
||||
| ((res[30] as u64) << 1 * 8)
|
||||
| ((res[31] as u64) << 0 * 8)
|
||||
}
|
||||
|
||||
fn script_for_p2wpkh(key: &PublicKey) -> Script {
|
||||
Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0)
|
||||
.push_slice(&WPubkeyHash::hash(&key.serialize())[..])
|
||||
.into_script()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -32,13 +32,12 @@ use util::ser::{Writeable, ReadableArgs, Readable};
|
|||
use util::config::UserConfig;
|
||||
|
||||
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
|
||||
use bitcoin::hashes::HashEngine;
|
||||
use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash};
|
||||
use bitcoin::hash_types::{Txid, BlockHash};
|
||||
use bitcoin::util::bip143;
|
||||
use bitcoin::util::address::Address;
|
||||
use bitcoin::util::bip32::{ChildNumber, ExtendedPubKey, ExtendedPrivKey};
|
||||
use bitcoin::blockdata::block::{Block, BlockHeader};
|
||||
use bitcoin::blockdata::transaction::{Transaction, TxOut, TxIn, SigHashType, OutPoint as BitcoinOutPoint};
|
||||
use bitcoin::blockdata::transaction::{Transaction, TxOut, TxIn, SigHashType};
|
||||
use bitcoin::blockdata::script::{Builder, Script};
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::blockdata::constants::genesis_block;
|
||||
|
@ -59,7 +58,7 @@ use std::sync::atomic::Ordering;
|
|||
use std::mem;
|
||||
|
||||
use ln::functional_test_utils::*;
|
||||
use ln::chan_utils::PreCalculatedTxCreationKeys;
|
||||
use ln::chan_utils::CommitmentTransaction;
|
||||
|
||||
#[test]
|
||||
fn test_insane_channel_opens() {
|
||||
|
@ -1617,20 +1616,20 @@ fn test_fee_spike_violation_fails_htlc() {
|
|||
|
||||
// Get the EnforcingChannelKeys 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_payment_point, local_secret, local_secret2) = {
|
||||
let (local_revocation_basepoint, local_htlc_basepoint, local_secret, local_secret2) = {
|
||||
let chan_lock = nodes[0].node.channel_state.lock().unwrap();
|
||||
let local_chan = chan_lock.by_id.get(&chan.2).unwrap();
|
||||
let chan_keys = local_chan.get_keys();
|
||||
let pubkeys = chan_keys.pubkeys();
|
||||
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, pubkeys.payment_point,
|
||||
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
|
||||
chan_keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER), chan_keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2))
|
||||
};
|
||||
let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_payment_point, remote_secret1) = {
|
||||
let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_secret1) = {
|
||||
let chan_lock = nodes[1].node.channel_state.lock().unwrap();
|
||||
let remote_chan = chan_lock.by_id.get(&chan.2).unwrap();
|
||||
let chan_keys = remote_chan.get_keys();
|
||||
let pubkeys = chan_keys.pubkeys();
|
||||
(pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, pubkeys.payment_point,
|
||||
(pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint,
|
||||
chan_keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1))
|
||||
};
|
||||
|
||||
|
@ -1643,70 +1642,31 @@ fn test_fee_spike_violation_fails_htlc() {
|
|||
// Build the remote commitment transaction so we can sign it, and then later use the
|
||||
// signature for the commitment_signed message.
|
||||
let local_chan_balance = 1313;
|
||||
let static_payment_pk = local_payment_point.serialize();
|
||||
let remote_commit_tx_output = TxOut {
|
||||
script_pubkey: Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0)
|
||||
.push_slice(&WPubkeyHash::hash(&static_payment_pk)[..])
|
||||
.into_script(),
|
||||
value: local_chan_balance as u64
|
||||
};
|
||||
|
||||
let local_commit_tx_output = TxOut {
|
||||
script_pubkey: chan_utils::get_revokeable_redeemscript(&commit_tx_keys.revocation_key,
|
||||
BREAKDOWN_TIMEOUT,
|
||||
&commit_tx_keys.broadcaster_delayed_payment_key).to_v0_p2wsh(),
|
||||
value: 95000,
|
||||
};
|
||||
|
||||
let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
|
||||
offered: false,
|
||||
amount_msat: 3460001,
|
||||
cltv_expiry: htlc_cltv,
|
||||
payment_hash: payment_hash,
|
||||
payment_hash,
|
||||
transaction_output_index: Some(1),
|
||||
};
|
||||
|
||||
let htlc_output = TxOut {
|
||||
script_pubkey: chan_utils::get_htlc_redeemscript(&accepted_htlc_info, &commit_tx_keys).to_v0_p2wsh(),
|
||||
value: 3460001 / 1000
|
||||
};
|
||||
let commitment_number = INITIAL_COMMITMENT_NUMBER - 1;
|
||||
|
||||
let commit_tx_obscure_factor = {
|
||||
let mut sha = Sha256::engine();
|
||||
let remote_payment_point = &remote_payment_point.serialize();
|
||||
sha.input(&local_payment_point.serialize());
|
||||
sha.input(remote_payment_point);
|
||||
let res = Sha256::from_engine(sha).into_inner();
|
||||
|
||||
((res[26] as u64) << 5*8) |
|
||||
((res[27] as u64) << 4*8) |
|
||||
((res[28] as u64) << 3*8) |
|
||||
((res[29] as u64) << 2*8) |
|
||||
((res[30] as u64) << 1*8) |
|
||||
((res[31] as u64) << 0*8)
|
||||
};
|
||||
let commitment_number = 1;
|
||||
let obscured_commitment_transaction_number = commit_tx_obscure_factor ^ commitment_number;
|
||||
let lock_time = ((0x20 as u32) << 8*3) | ((obscured_commitment_transaction_number & 0xffffffu64) as u32);
|
||||
let input = TxIn {
|
||||
previous_output: BitcoinOutPoint { txid: chan.3.txid(), vout: 0 },
|
||||
script_sig: Script::new(),
|
||||
sequence: ((0x80 as u32) << 8*3) | ((obscured_commitment_transaction_number >> 3*8) as u32),
|
||||
witness: Vec::new(),
|
||||
};
|
||||
|
||||
let commit_tx = Transaction {
|
||||
version: 2,
|
||||
lock_time,
|
||||
input: vec![input],
|
||||
output: vec![remote_commit_tx_output, htlc_output, local_commit_tx_output],
|
||||
};
|
||||
let res = {
|
||||
let local_chan_lock = nodes[0].node.channel_state.lock().unwrap();
|
||||
let local_chan = local_chan_lock.by_id.get(&chan.2).unwrap();
|
||||
let local_chan_keys = local_chan.get_keys();
|
||||
let pre_commit_tx_keys = PreCalculatedTxCreationKeys::new(commit_tx_keys);
|
||||
local_chan_keys.sign_counterparty_commitment(feerate_per_kw, &commit_tx, &pre_commit_tx_keys, &[&accepted_htlc_info], &secp_ctx).unwrap()
|
||||
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
|
||||
commitment_number,
|
||||
95000,
|
||||
local_chan_balance,
|
||||
commit_tx_keys.clone(),
|
||||
feerate_per_kw,
|
||||
&mut vec![(accepted_htlc_info, ())],
|
||||
&local_chan.channel_transaction_parameters.as_counterparty_broadcastable()
|
||||
);
|
||||
local_chan_keys.sign_counterparty_commitment(&commitment_tx, &secp_ctx).unwrap()
|
||||
};
|
||||
|
||||
let commit_signed_msg = msgs::CommitmentSigned {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
//! The logic to build claims and bump in-flight transactions until confirmations.
|
||||
//!
|
||||
//! OnchainTxHandler objetcs are fully-part of ChannelMonitor and encapsulates all
|
||||
//! OnchainTxHandler objects are fully-part of ChannelMonitor and encapsulates all
|
||||
//! building, tracking, bumping and notifications functions.
|
||||
|
||||
use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut, SigHashType};
|
||||
|
@ -24,7 +24,7 @@ use bitcoin::secp256k1;
|
|||
use ln::msgs::DecodeError;
|
||||
use ln::channelmanager::PaymentPreimage;
|
||||
use ln::chan_utils;
|
||||
use ln::chan_utils::{TxCreationKeys, HolderCommitmentTransaction};
|
||||
use ln::chan_utils::{TxCreationKeys, ChannelTransactionParameters, HolderCommitmentTransaction};
|
||||
use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
|
||||
use chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest};
|
||||
use chain::keysinterface::ChannelKeys;
|
||||
|
@ -244,14 +244,13 @@ pub struct OnchainTxHandler<ChanSigner: ChannelKeys> {
|
|||
holder_commitment: Option<HolderCommitmentTransaction>,
|
||||
// holder_htlc_sigs and prev_holder_htlc_sigs are in the order as they appear in the commitment
|
||||
// transaction outputs (hence the Option<>s inside the Vec). The first usize is the index in
|
||||
// the set of HTLCs in the HolderCommitmentTransaction (including those which do not appear in
|
||||
// the commitment transaction).
|
||||
// the set of HTLCs in the HolderCommitmentTransaction.
|
||||
holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>>,
|
||||
prev_holder_commitment: Option<HolderCommitmentTransaction>,
|
||||
prev_holder_htlc_sigs: Option<Vec<Option<(usize, Signature)>>>,
|
||||
on_holder_tx_csv: u16,
|
||||
|
||||
key_storage: ChanSigner,
|
||||
pub(crate) channel_transaction_parameters: ChannelTransactionParameters,
|
||||
|
||||
// Used to track claiming requests. If claim tx doesn't confirm before height timer expiration we need to bump
|
||||
// it (RBF or CPFP). If an input has been part of an aggregate tx at first claim try, we need to keep it within
|
||||
|
@ -295,9 +294,8 @@ impl<ChanSigner: ChannelKeys + Writeable> OnchainTxHandler<ChanSigner> {
|
|||
self.prev_holder_commitment.write(writer)?;
|
||||
self.prev_holder_htlc_sigs.write(writer)?;
|
||||
|
||||
self.on_holder_tx_csv.write(writer)?;
|
||||
|
||||
self.key_storage.write(writer)?;
|
||||
self.channel_transaction_parameters.write(writer)?;
|
||||
|
||||
writer.write_all(&byte_utils::be64_to_array(self.pending_claim_requests.len() as u64))?;
|
||||
for (ref ancestor_claim_txid, claim_tx_data) in self.pending_claim_requests.iter() {
|
||||
|
@ -344,9 +342,8 @@ impl<ChanSigner: ChannelKeys + Readable> Readable for OnchainTxHandler<ChanSigne
|
|||
let prev_holder_commitment = Readable::read(reader)?;
|
||||
let prev_holder_htlc_sigs = Readable::read(reader)?;
|
||||
|
||||
let on_holder_tx_csv = Readable::read(reader)?;
|
||||
|
||||
let key_storage = Readable::read(reader)?;
|
||||
let channel_parameters = Readable::read(reader)?;
|
||||
|
||||
let pending_claim_requests_len: u64 = Readable::read(reader)?;
|
||||
let mut pending_claim_requests = HashMap::with_capacity(cmp::min(pending_claim_requests_len as usize, MAX_ALLOC_SIZE / 128));
|
||||
|
@ -398,8 +395,8 @@ impl<ChanSigner: ChannelKeys + Readable> Readable for OnchainTxHandler<ChanSigne
|
|||
holder_htlc_sigs,
|
||||
prev_holder_commitment,
|
||||
prev_holder_htlc_sigs,
|
||||
on_holder_tx_csv,
|
||||
key_storage,
|
||||
channel_transaction_parameters: channel_parameters,
|
||||
claimable_outpoints,
|
||||
pending_claim_requests,
|
||||
onchain_events_waiting_threshold_conf,
|
||||
|
@ -410,7 +407,7 @@ impl<ChanSigner: ChannelKeys + Readable> Readable for OnchainTxHandler<ChanSigne
|
|||
}
|
||||
|
||||
impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
|
||||
pub(crate) fn new(destination_script: Script, keys: ChanSigner, on_holder_tx_csv: u16) -> Self {
|
||||
pub(crate) fn new(destination_script: Script, keys: ChanSigner, channel_parameters: ChannelTransactionParameters) -> Self {
|
||||
|
||||
let key_storage = keys;
|
||||
|
||||
|
@ -420,8 +417,8 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
|
|||
holder_htlc_sigs: None,
|
||||
prev_holder_commitment: None,
|
||||
prev_holder_htlc_sigs: None,
|
||||
on_holder_tx_csv,
|
||||
key_storage,
|
||||
channel_transaction_parameters: channel_parameters,
|
||||
pending_claim_requests: HashMap::new(),
|
||||
claimable_outpoints: HashMap::new(),
|
||||
onchain_events_waiting_threshold_conf: HashMap::new(),
|
||||
|
@ -654,7 +651,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
|
|||
let signed_tx = self.get_fully_signed_holder_tx(funding_redeemscript).unwrap();
|
||||
// Timer set to $NEVER given we can't bump tx without anchor outputs
|
||||
log_trace!(logger, "Going to broadcast Holder Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid);
|
||||
return Some((None, self.holder_commitment.as_ref().unwrap().feerate_per_kw, signed_tx));
|
||||
return Some((None, self.holder_commitment.as_ref().unwrap().feerate_per_kw(), signed_tx));
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
|
@ -899,44 +896,39 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
|
|||
fn sign_latest_holder_htlcs(&mut self) {
|
||||
if let Some(ref holder_commitment) = self.holder_commitment {
|
||||
if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) {
|
||||
self.holder_htlc_sigs = Some(Vec::new());
|
||||
let ret = self.holder_htlc_sigs.as_mut().unwrap();
|
||||
for (htlc_idx, (holder_sig, &(ref htlc, _))) in sigs.iter().zip(holder_commitment.per_htlc.iter()).enumerate() {
|
||||
if let Some(tx_idx) = htlc.transaction_output_index {
|
||||
if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); }
|
||||
ret[tx_idx as usize] = Some((htlc_idx, holder_sig.expect("Did not receive a signature for a non-dust HTLC")));
|
||||
} else {
|
||||
assert!(holder_sig.is_none(), "Received a signature for a dust HTLC");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn sign_prev_holder_htlcs(&mut self) {
|
||||
if let Some(ref holder_commitment) = self.prev_holder_commitment {
|
||||
if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) {
|
||||
self.prev_holder_htlc_sigs = Some(Vec::new());
|
||||
let ret = self.prev_holder_htlc_sigs.as_mut().unwrap();
|
||||
for (htlc_idx, (holder_sig, &(ref htlc, _))) in sigs.iter().zip(holder_commitment.per_htlc.iter()).enumerate() {
|
||||
if let Some(tx_idx) = htlc.transaction_output_index {
|
||||
if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); }
|
||||
ret[tx_idx as usize] = Some((htlc_idx, holder_sig.expect("Did not receive a signature for a non-dust HTLC")));
|
||||
} else {
|
||||
assert!(holder_sig.is_none(), "Received a signature for a dust HTLC");
|
||||
}
|
||||
}
|
||||
self.holder_htlc_sigs = Some(Self::extract_holder_sigs(holder_commitment, sigs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: getting lastest holder transactions should be infaillible and result in us "force-closing the channel", but we may
|
||||
fn sign_prev_holder_htlcs(&mut self) {
|
||||
if let Some(ref holder_commitment) = self.prev_holder_commitment {
|
||||
if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) {
|
||||
self.prev_holder_htlc_sigs = Some(Self::extract_holder_sigs(holder_commitment, sigs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_holder_sigs(holder_commitment: &HolderCommitmentTransaction, sigs: Vec<Signature>) -> Vec<Option<(usize, Signature)>> {
|
||||
let mut ret = Vec::new();
|
||||
for (htlc_idx, (holder_sig, htlc)) in sigs.iter().zip(holder_commitment.htlcs().iter()).enumerate() {
|
||||
let tx_idx = htlc.transaction_output_index.unwrap();
|
||||
if ret.len() <= tx_idx as usize { ret.resize(tx_idx as usize + 1, None); }
|
||||
ret[tx_idx as usize] = Some((htlc_idx, holder_sig.clone()));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
//TODO: getting lastest holder transactions should be infallible and result in us "force-closing the channel", but we may
|
||||
// have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after Channel::get_outbound_funding_created,
|
||||
// before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing
|
||||
// to monitor before.
|
||||
pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Option<Transaction> {
|
||||
if let Some(ref mut holder_commitment) = self.holder_commitment {
|
||||
match self.key_storage.sign_holder_commitment(holder_commitment, &self.secp_ctx) {
|
||||
Ok(sig) => Some(holder_commitment.add_holder_sig(funding_redeemscript, sig)),
|
||||
match self.key_storage.sign_holder_commitment(&holder_commitment, &self.secp_ctx) {
|
||||
Ok(sig) => {
|
||||
Some(holder_commitment.add_holder_sig(funding_redeemscript, sig))
|
||||
},
|
||||
Err(_) => return None,
|
||||
}
|
||||
} else {
|
||||
|
@ -947,9 +939,10 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
|
|||
#[cfg(any(test, feature="unsafe_revoked_tx_signing"))]
|
||||
pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Option<Transaction> {
|
||||
if let Some(ref mut holder_commitment) = self.holder_commitment {
|
||||
let holder_commitment = holder_commitment.clone();
|
||||
match self.key_storage.sign_holder_commitment(&holder_commitment, &self.secp_ctx) {
|
||||
Ok(sig) => Some(holder_commitment.add_holder_sig(funding_redeemscript, sig)),
|
||||
match self.key_storage.sign_holder_commitment(holder_commitment, &self.secp_ctx) {
|
||||
Ok(sig) => {
|
||||
Some(holder_commitment.add_holder_sig(funding_redeemscript, sig))
|
||||
},
|
||||
Err(_) => return None,
|
||||
}
|
||||
} else {
|
||||
|
@ -960,24 +953,30 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
|
|||
pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
|
||||
let mut htlc_tx = None;
|
||||
if self.holder_commitment.is_some() {
|
||||
let commitment_txid = self.holder_commitment.as_ref().unwrap().txid();
|
||||
let commitment_txid = self.holder_commitment.as_ref().unwrap().trust().txid();
|
||||
if commitment_txid == outp.txid {
|
||||
self.sign_latest_holder_htlcs();
|
||||
if let &Some(ref htlc_sigs) = &self.holder_htlc_sigs {
|
||||
let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
|
||||
htlc_tx = Some(self.holder_commitment.as_ref().unwrap()
|
||||
.get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.on_holder_tx_csv));
|
||||
let holder_commitment = self.holder_commitment.as_ref().unwrap();
|
||||
let trusted_tx = holder_commitment.trust();
|
||||
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[*htlc_idx];
|
||||
htlc_tx = Some(trusted_tx
|
||||
.get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage));
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.prev_holder_commitment.is_some() {
|
||||
let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().txid();
|
||||
let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().trust().txid();
|
||||
if commitment_txid == outp.txid {
|
||||
self.sign_prev_holder_htlcs();
|
||||
if let &Some(ref htlc_sigs) = &self.prev_holder_htlc_sigs {
|
||||
let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
|
||||
htlc_tx = Some(self.prev_holder_commitment.as_ref().unwrap()
|
||||
.get_signed_htlc_tx(*htlc_idx, htlc_sig, preimage, self.on_holder_tx_csv));
|
||||
let holder_commitment = self.prev_holder_commitment.as_ref().unwrap();
|
||||
let trusted_tx = holder_commitment.trust();
|
||||
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[*htlc_idx];
|
||||
htlc_tx = Some(trusted_tx
|
||||
.get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
use ln::chan_utils::{HTLCOutputInCommitment, TxCreationKeys, ChannelPublicKeys, HolderCommitmentTransaction, PreCalculatedTxCreationKeys};
|
||||
use ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction};
|
||||
use ln::{chan_utils, msgs};
|
||||
use chain::keysinterface::{ChannelKeys, InMemoryChannelKeys};
|
||||
|
||||
|
@ -24,38 +24,25 @@ use util::ser::{Writeable, Writer, Readable};
|
|||
use std::io::Error;
|
||||
use ln::msgs::DecodeError;
|
||||
|
||||
/// Enforces some rules on ChannelKeys calls. Eventually we will probably want to expose a variant
|
||||
/// of this which would essentially be what you'd want to run on a hardware wallet.
|
||||
/// An implementation of ChannelKeys that enforces some policy checks.
|
||||
///
|
||||
/// Eventually we will probably want to expose a variant of this which would essentially
|
||||
/// be what you'd want to run on a hardware wallet.
|
||||
#[derive(Clone)]
|
||||
pub struct EnforcingChannelKeys {
|
||||
pub inner: InMemoryChannelKeys,
|
||||
commitment_number_obscure_and_last: Arc<Mutex<(Option<u64>, u64)>>,
|
||||
last_commitment_number: Arc<Mutex<Option<u64>>>,
|
||||
}
|
||||
|
||||
impl EnforcingChannelKeys {
|
||||
pub fn new(inner: InMemoryChannelKeys) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
commitment_number_obscure_and_last: Arc::new(Mutex::new((None, 0))),
|
||||
last_commitment_number: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EnforcingChannelKeys {
|
||||
fn check_keys<T: secp256k1::Signing + secp256k1::Verification>(&self, secp_ctx: &Secp256k1<T>,
|
||||
keys: &TxCreationKeys) {
|
||||
let remote_points = self.inner.counterparty_pubkeys();
|
||||
|
||||
let keys_expected = TxCreationKeys::derive_new(secp_ctx,
|
||||
&keys.per_commitment_point,
|
||||
&remote_points.delayed_payment_basepoint,
|
||||
&remote_points.htlc_basepoint,
|
||||
&self.inner.pubkeys().revocation_basepoint,
|
||||
&self.inner.pubkeys().htlc_basepoint).unwrap();
|
||||
if keys != &keys_expected { panic!("derived different per-tx keys") }
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelKeys for EnforcingChannelKeys {
|
||||
fn get_per_commitment_point<T: secp256k1::Signing + secp256k1::Verification>(&self, idx: u64, secp_ctx: &Secp256k1<T>) -> PublicKey {
|
||||
self.inner.get_per_commitment_point(idx, secp_ctx)
|
||||
|
@ -69,51 +56,52 @@ impl ChannelKeys for EnforcingChannelKeys {
|
|||
fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() }
|
||||
fn key_derivation_params(&self) -> (u64, u64) { self.inner.key_derivation_params() }
|
||||
|
||||
fn sign_counterparty_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, feerate_per_kw: u32, commitment_tx: &Transaction, pre_keys: &PreCalculatedTxCreationKeys, htlcs: &[&HTLCOutputInCommitment], secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
|
||||
if commitment_tx.input.len() != 1 { panic!("lightning commitment transactions have a single input"); }
|
||||
self.check_keys(secp_ctx, pre_keys.trust_key_derivation());
|
||||
let obscured_commitment_transaction_number = (commitment_tx.lock_time & 0xffffff) as u64 | ((commitment_tx.input[0].sequence as u64 & 0xffffff) << 3*8);
|
||||
fn sign_counterparty_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
|
||||
self.verify_counterparty_commitment_tx(commitment_tx, secp_ctx);
|
||||
|
||||
{
|
||||
let mut commitment_data = self.commitment_number_obscure_and_last.lock().unwrap();
|
||||
if commitment_data.0.is_none() {
|
||||
commitment_data.0 = Some(obscured_commitment_transaction_number ^ commitment_data.1);
|
||||
}
|
||||
let commitment_number = obscured_commitment_transaction_number ^ commitment_data.0.unwrap();
|
||||
assert!(commitment_number == commitment_data.1 || commitment_number == commitment_data.1 + 1);
|
||||
commitment_data.1 = cmp::max(commitment_number, commitment_data.1)
|
||||
let mut last_commitment_number_guard = self.last_commitment_number.lock().unwrap();
|
||||
let actual_commitment_number = commitment_tx.commitment_number();
|
||||
let last_commitment_number = last_commitment_number_guard.unwrap_or(actual_commitment_number);
|
||||
// These commitment numbers are backwards counting. We expect either the same as the previously encountered,
|
||||
// or the next one.
|
||||
assert!(last_commitment_number == actual_commitment_number || last_commitment_number - 1 == actual_commitment_number, "{} doesn't come after {}", actual_commitment_number, last_commitment_number);
|
||||
*last_commitment_number_guard = Some(cmp::min(last_commitment_number, actual_commitment_number))
|
||||
}
|
||||
|
||||
Ok(self.inner.sign_counterparty_commitment(feerate_per_kw, commitment_tx, pre_keys, htlcs, secp_ctx).unwrap())
|
||||
Ok(self.inner.sign_counterparty_commitment(commitment_tx, secp_ctx).unwrap())
|
||||
}
|
||||
|
||||
fn sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
fn sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
self.verify_holder_commitment_tx(commitment_tx, secp_ctx);
|
||||
|
||||
// TODO: enforce the ChannelKeys contract - error if this commitment was already revoked
|
||||
// TODO: need the commitment number
|
||||
Ok(self.inner.sign_holder_commitment(holder_commitment_tx, secp_ctx).unwrap())
|
||||
Ok(self.inner.sign_holder_commitment(commitment_tx, secp_ctx).unwrap())
|
||||
}
|
||||
|
||||
#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
|
||||
fn unsafe_sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
Ok(self.inner.unsafe_sign_holder_commitment(holder_commitment_tx, secp_ctx).unwrap())
|
||||
fn unsafe_sign_holder_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
Ok(self.inner.unsafe_sign_holder_commitment(commitment_tx, secp_ctx).unwrap())
|
||||
}
|
||||
|
||||
fn sign_holder_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, holder_commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()> {
|
||||
let commitment_txid = holder_commitment_tx.txid();
|
||||
fn sign_holder_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Vec<Signature>, ()> {
|
||||
let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx);
|
||||
let commitment_txid = trusted_tx.txid();
|
||||
let holder_csv = self.inner.counterparty_selected_contest_delay();
|
||||
|
||||
for this_htlc in holder_commitment_tx.per_htlc.iter() {
|
||||
if this_htlc.0.transaction_output_index.is_some() {
|
||||
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, holder_commitment_tx.feerate_per_kw, holder_csv, &this_htlc.0, &holder_commitment_tx.keys.broadcaster_delayed_payment_key, &holder_commitment_tx.keys.revocation_key);
|
||||
for (this_htlc, sig) in trusted_tx.htlcs().iter().zip(&commitment_tx.counterparty_htlc_sigs) {
|
||||
assert!(this_htlc.transaction_output_index.is_some());
|
||||
let keys = trusted_tx.keys();
|
||||
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, trusted_tx.feerate_per_kw(), holder_csv, &this_htlc, &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
|
||||
|
||||
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&this_htlc.0, &holder_commitment_tx.keys);
|
||||
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&this_htlc, &keys);
|
||||
|
||||
let sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, this_htlc.0.amount_msat / 1000, SigHashType::All)[..]);
|
||||
secp_ctx.verify(&sighash, this_htlc.1.as_ref().unwrap(), &holder_commitment_tx.keys.countersignatory_htlc_key).unwrap();
|
||||
}
|
||||
let sighash = hash_to_message!(&bip143::SigHashCache::new(&htlc_tx).signature_hash(0, &htlc_redeemscript, this_htlc.amount_msat / 1000, SigHashType::All)[..]);
|
||||
secp_ctx.verify(&sighash, sig, &keys.countersignatory_htlc_key).unwrap();
|
||||
}
|
||||
|
||||
Ok(self.inner.sign_holder_commitment_htlc_transactions(holder_commitment_tx, secp_ctx).unwrap())
|
||||
Ok(self.inner.sign_holder_commitment_htlc_transactions(commitment_tx, secp_ctx).unwrap())
|
||||
}
|
||||
|
||||
fn sign_justice_transaction<T: secp256k1::Signing + secp256k1::Verification>(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &Option<HTLCOutputInCommitment>, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
|
||||
|
@ -132,16 +120,16 @@ impl ChannelKeys for EnforcingChannelKeys {
|
|||
self.inner.sign_channel_announcement(msg, secp_ctx)
|
||||
}
|
||||
|
||||
fn on_accept(&mut self, channel_pubkeys: &ChannelPublicKeys, counterparty_selected_delay: u16, holder_selected_delay: u16) {
|
||||
self.inner.on_accept(channel_pubkeys, counterparty_selected_delay, holder_selected_delay)
|
||||
fn ready_channel(&mut self, channel_parameters: &ChannelTransactionParameters) {
|
||||
self.inner.ready_channel(channel_parameters)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Writeable for EnforcingChannelKeys {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
self.inner.write(writer)?;
|
||||
let (obscure, last) = *self.commitment_number_obscure_and_last.lock().unwrap();
|
||||
obscure.write(writer)?;
|
||||
let last = *self.last_commitment_number.lock().unwrap();
|
||||
last.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -150,10 +138,24 @@ impl Writeable for EnforcingChannelKeys {
|
|||
impl Readable for EnforcingChannelKeys {
|
||||
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
|
||||
let inner = Readable::read(reader)?;
|
||||
let obscure_and_last = Readable::read(reader)?;
|
||||
let last_commitment_number = Readable::read(reader)?;
|
||||
Ok(EnforcingChannelKeys {
|
||||
inner: inner,
|
||||
commitment_number_obscure_and_last: Arc::new(Mutex::new(obscure_and_last))
|
||||
inner,
|
||||
last_commitment_number: Arc::new(Mutex::new(last_commitment_number))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EnforcingChannelKeys {
|
||||
fn verify_counterparty_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
|
||||
commitment_tx.verify(&self.inner.get_channel_parameters().as_counterparty_broadcastable(),
|
||||
self.inner.counterparty_pubkeys(), self.inner.pubkeys(), secp_ctx)
|
||||
.expect("derived different per-tx keys or built transaction")
|
||||
}
|
||||
|
||||
fn verify_holder_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
|
||||
commitment_tx.verify(&self.inner.get_channel_parameters().as_holder_broadcastable(),
|
||||
self.inner.pubkeys(), self.inner.counterparty_pubkeys(), secp_ctx)
|
||||
.expect("derived different per-tx keys or built transaction")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ use util::byte_utils;
|
|||
|
||||
use util::byte_utils::{be64_to_array, be48_to_array, be32_to_array, be16_to_array, slice_to_be16, slice_to_be32, slice_to_be48, slice_to_be64};
|
||||
|
||||
const MAX_BUF_SIZE: usize = 64 * 1024;
|
||||
/// serialization buffer size
|
||||
pub const MAX_BUF_SIZE: usize = 64 * 1024;
|
||||
|
||||
/// A trait that is similar to std::io::Write but has one extra function which can be used to size
|
||||
/// buffers being written into.
|
||||
|
|
Loading…
Add table
Reference in a new issue