use core::any::Any;
use core::fmt;
use core::fmt::{Debug, Error, Formatter};
use bitcoin::hashes::hex::{self, FromHex};
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{self, ecdsa::Signature, All, Message, PublicKey, Secp256k1, SecretKey};
use bitcoin::util::sighash::SighashCache;
use bitcoin::{EcdsaSighashType, Network, OutPoint, Script, Transaction};
use lightning::chain;
use lightning::ln::chan_utils::{
build_htlc_transaction, derive_private_key, derive_public_revocation_key,
get_htlc_redeemscript, make_funding_redeemscript, ChannelPublicKeys,
ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction,
CounterpartyChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction,
TxCreationKeys,
};
use lightning::ln::features::ChannelTypeFeatures;
use lightning::ln::{chan_utils, PaymentHash, PaymentPreimage};
use lightning::sign::{
ChannelSigner, EcdsaChannelSigner, EntropySource, InMemorySigner, SignerProvider,
};
use log::*;
use serde_derive::{Deserialize, Serialize};
use serde_with::{hex::Hex, serde_as, Bytes, IfIsHumanReadable};
use crate::monitor::ChainMonitorBase;
use crate::node::{Node, RoutedPayment};
use crate::policy::error::policy_error;
use crate::policy::validator::{ChainState, CommitmentSignatures, EnforcementState, Validator};
use crate::prelude::*;
use crate::tx::script::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::tx::tx::{CommitmentInfo2, HTLCInfo2};
use crate::util::crypto_utils::derive_public_key;
use crate::util::debug_utils::{DebugHTLCOutputInCommitment, DebugInMemorySigner, DebugVecVecU8};
use crate::util::ser_util::{ChannelPublicKeysDef, OutPointDef, ScriptDef};
use crate::util::status::{internal_error, invalid_argument, Status};
use crate::util::transaction_utils::add_holder_sig;
use crate::util::INITIAL_COMMITMENT_NUMBER;
use crate::wallet::Wallet;
use crate::{catch_panic, policy_err, Arc, CommitmentPointProvider, Weak};
#[serde_as]
#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ChannelId(#[serde_as(as = "IfIsHumanReadable<Hex, Bytes>")] Vec<u8>);
impl ChannelId {
pub fn new(inner: &[u8]) -> Self {
Self(inner.to_vec())
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
pub fn inner(&self) -> &Vec<u8> {
&self.0
}
}
impl Debug for ChannelId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
hex::format_hex(&self.0, f)
}
}
impl fmt::Display for ChannelId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
hex::format_hex(&self.0, f)
}
}
#[derive(Debug)]
pub struct TypedSignature {
pub sig: Signature,
pub typ: EcdsaSighashType,
}
impl TypedSignature {
pub fn serialize(&self) -> Vec<u8> {
let mut ss = self.sig.serialize_der().to_vec();
ss.push(self.typ as u8);
ss
}
pub fn all(sig: Signature) -> Self {
Self { sig, typ: EcdsaSighashType::All }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum CommitmentType {
Legacy,
StaticRemoteKey,
Anchors,
AnchorsZeroFeeHtlc,
}
#[serde_as]
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct ChannelSetup {
pub is_outbound: bool,
pub channel_value_sat: u64,
pub push_value_msat: u64,
#[serde_as(as = "IfIsHumanReadable<OutPointDef>")]
pub funding_outpoint: OutPoint,
pub holder_selected_contest_delay: u16,
#[serde_as(as = "IfIsHumanReadable<Option<ScriptDef>>")]
pub holder_shutdown_script: Option<Script>,
#[serde_as(as = "ChannelPublicKeysDef")]
pub counterparty_points: ChannelPublicKeys,
pub counterparty_selected_contest_delay: u16,
#[serde_as(as = "IfIsHumanReadable<Option<ScriptDef>>")]
pub counterparty_shutdown_script: Option<Script>,
pub commitment_type: CommitmentType,
}
impl fmt::Debug for ChannelSetup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChannelSetup")
.field("is_outbound", &self.is_outbound)
.field("channel_value_sat", &self.channel_value_sat)
.field("push_value_msat", &self.push_value_msat)
.field("funding_outpoint", &self.funding_outpoint)
.field("holder_selected_contest_delay", &self.holder_selected_contest_delay)
.field("holder_shutdown_script", &self.holder_shutdown_script)
.field("counterparty_points", log_channel_public_keys!(&self.counterparty_points))
.field("counterparty_selected_contest_delay", &self.counterparty_selected_contest_delay)
.field("counterparty_shutdown_script", &self.counterparty_shutdown_script)
.field("commitment_type", &self.commitment_type)
.finish()
}
}
impl ChannelSetup {
pub fn is_static_remotekey(&self) -> bool {
self.commitment_type != CommitmentType::Legacy
}
pub fn is_anchors(&self) -> bool {
self.commitment_type == CommitmentType::Anchors
|| self.commitment_type == CommitmentType::AnchorsZeroFeeHtlc
}
pub fn is_zero_fee_htlc(&self) -> bool {
self.commitment_type == CommitmentType::AnchorsZeroFeeHtlc
}
pub fn features(&self) -> ChannelTypeFeatures {
let mut features = ChannelTypeFeatures::empty();
features.set_static_remote_key_required();
if self.is_anchors() {
if self.is_zero_fee_htlc() {
features.set_anchors_zero_fee_htlc_tx_optional();
} else {
features.set_anchors_nonzero_fee_htlc_tx_optional();
}
}
features
}
}
pub trait ChannelBase: Any {
fn get_channel_basepoints(&self) -> ChannelPublicKeys;
fn get_per_commitment_point(&self, commitment_number: u64) -> Result<PublicKey, Status>;
fn get_per_commitment_secret(&self, commitment_number: u64) -> Result<SecretKey, Status>;
fn check_future_secret(&self, commit_num: u64, suggested: &SecretKey) -> Result<bool, Status>;
fn validator(&self) -> Arc<dyn Validator>;
#[allow(missing_docs)]
#[cfg(any(test, feature = "test_utils"))]
fn set_next_holder_commit_num_for_testing(&mut self, _num: u64) {
}
}
#[derive(Debug, Clone)]
pub enum ChannelSlot {
Stub(ChannelStub),
Ready(Channel),
}
impl ChannelSlot {
pub fn id(&self) -> ChannelId {
match self {
ChannelSlot::Stub(stub) => stub.id0.clone(),
ChannelSlot::Ready(chan) => chan.id0.clone(),
}
}
pub fn get_channel_basepoints(&self) -> ChannelPublicKeys {
match self {
ChannelSlot::Stub(stub) => stub.get_channel_basepoints(),
ChannelSlot::Ready(chan) => chan.get_channel_basepoints(),
}
}
pub fn unwrap_stub(&self) -> &ChannelStub {
match self {
ChannelSlot::Stub(stub) => stub,
ChannelSlot::Ready(_) => panic!("unwrap_stub called on ChannelSlot::Ready"),
}
}
}
#[derive(Clone)]
pub struct ChannelStub {
pub node: Weak<Node>,
pub(crate) secp_ctx: Secp256k1<All>,
pub keys: InMemorySigner,
pub id0: ChannelId,
pub blockheight: u32,
}
impl fmt::Debug for ChannelStub {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChannelStub")
.field("keys", &DebugInMemorySigner(&self.keys))
.field("id0", &self.id0)
.finish()
}
}
impl ChannelBase for ChannelStub {
fn get_channel_basepoints(&self) -> ChannelPublicKeys {
self.keys.pubkeys().clone()
}
fn get_per_commitment_point(&self, commitment_number: u64) -> Result<PublicKey, Status> {
if ![0, 1].contains(&commitment_number) {
return Err(policy_error(format!(
"channel stub can only return point for commitment number zero or one",
))
.into());
}
Ok(self.keys.get_per_commitment_point(
INITIAL_COMMITMENT_NUMBER - commitment_number,
&self.secp_ctx,
))
}
fn get_per_commitment_secret(&self, _commitment_number: u64) -> Result<SecretKey, Status> {
Err(policy_error(format!("channel stub cannot release commitment secret")).into())
}
fn check_future_secret(
&self,
commitment_number: u64,
suggested: &SecretKey,
) -> Result<bool, Status> {
let secret_data =
self.keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - commitment_number);
Ok(suggested[..] == secret_data)
}
fn validator(&self) -> Arc<dyn Validator> {
let node = self.node.upgrade().unwrap();
let v = node.validator_factory.lock().unwrap().make_validator(
node.network(),
node.get_id(),
Some(self.id0.clone()),
);
v
}
}
impl ChannelStub {
pub(crate) fn channel_keys_with_channel_value(&self, channel_value_sat: u64) -> InMemorySigner {
let secp_ctx = Secp256k1::signing_only();
let keys = &self.keys;
InMemorySigner::new(
&secp_ctx,
keys.funding_key,
keys.revocation_base_key,
keys.payment_key,
keys.delayed_payment_base_key,
keys.htlc_base_key,
keys.commitment_seed,
channel_value_sat,
keys.channel_keys_id(),
keys.get_secure_random_bytes(),
)
}
}
#[derive(Clone)]
pub struct Channel {
pub node: Weak<Node>,
pub(crate) secp_ctx: Secp256k1<All>,
pub keys: InMemorySigner,
pub enforcement_state: EnforcementState,
pub setup: ChannelSetup,
pub id0: ChannelId,
pub id: Option<ChannelId>,
pub monitor: ChainMonitorBase,
}
impl Debug for Channel {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("channel")
}
}
impl ChannelBase for Channel {
#[cfg(any(test, feature = "test_utils"))]
fn set_next_holder_commit_num_for_testing(&mut self, num: u64) {
self.enforcement_state.set_next_holder_commit_num_for_testing(num);
}
fn get_channel_basepoints(&self) -> ChannelPublicKeys {
self.keys.pubkeys().clone()
}
fn get_per_commitment_point(&self, commitment_number: u64) -> Result<PublicKey, Status> {
let next_holder_commit_num = self.enforcement_state.next_holder_commit_num;
if commitment_number > next_holder_commit_num + 1 {
return Err(policy_error(format!(
"get_per_commitment_point: \
commitment_number {} invalid when next_holder_commit_num is {}",
commitment_number, next_holder_commit_num,
))
.into());
}
Ok(self.get_per_commitment_point_unchecked(commitment_number))
}
fn get_per_commitment_secret(&self, commitment_number: u64) -> Result<SecretKey, Status> {
let next_holder_commit_num = self.enforcement_state.next_holder_commit_num;
if commitment_number + 2 > next_holder_commit_num {
let validator = self.validator();
policy_err!(
validator,
"policy-revoke-new-commitment-signed",
"cannot revoke commitment_number {} when next_holder_commit_num is {}",
commitment_number,
next_holder_commit_num,
)
}
let secret =
self.keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - commitment_number);
Ok(SecretKey::from_slice(&secret).unwrap())
}
fn check_future_secret(
&self,
commitment_number: u64,
suggested: &SecretKey,
) -> Result<bool, Status> {
let secret_data =
self.keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - commitment_number);
Ok(suggested[..] == secret_data)
}
fn validator(&self) -> Arc<dyn Validator> {
let node = self.get_node();
let v = node.validator_factory.lock().unwrap().make_validator(
self.network(),
node.get_id(),
Some(self.id0.clone()),
);
v
}
}
impl Channel {
pub fn id(&self) -> ChannelId {
self.id.clone().unwrap_or(self.id0.clone())
}
#[allow(missing_docs)]
#[cfg(any(test, feature = "test_utils"))]
pub fn set_next_counterparty_commit_num_for_testing(
&mut self,
num: u64,
current_point: PublicKey,
) {
self.enforcement_state.set_next_counterparty_commit_num_for_testing(num, current_point);
}
#[allow(missing_docs)]
#[cfg(any(test, feature = "test_utils"))]
pub fn set_next_counterparty_revoke_num_for_testing(&mut self, num: u64) {
self.enforcement_state.set_next_counterparty_revoke_num_for_testing(num);
}
pub(crate) fn get_chain_state(&self) -> ChainState {
self.monitor.as_chain_state()
}
fn get_per_commitment_point_unchecked(&self, commitment_number: u64) -> PublicKey {
self.keys
.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - commitment_number, &self.secp_ctx)
}
pub(crate) fn get_counterparty_commitment_point(
&self,
commitment_number: u64,
) -> Option<PublicKey> {
let state = &self.enforcement_state;
let next_commit_num = state.next_counterparty_commit_num;
if next_commit_num < commitment_number + 1 {
warn!("asked for counterparty commitment point {} but our next counterparty commitment number is {}",
commitment_number, next_commit_num);
None
} else if next_commit_num == commitment_number + 1 {
state.current_counterparty_point
} else if next_commit_num == commitment_number {
state.previous_counterparty_point
} else if let Some(secrets) = state.counterparty_secrets.as_ref() {
let secret = secrets.get_secret(INITIAL_COMMITMENT_NUMBER - commitment_number);
secret.map(|s| {
PublicKey::from_secret_key(
&self.secp_ctx,
&SecretKey::from_slice(&s).expect("secret from storage"),
)
})
} else {
warn!(
"asked for counterparty commitment point {} but we don't have secrets storage",
commitment_number
);
None
}
}
}
impl Channel {
pub fn make_counterparty_tx_keys(
&self,
per_commitment_point: &PublicKey,
) -> Result<TxCreationKeys, Status> {
let holder_points = self.keys.pubkeys();
let counterparty_points = self.keys.counterparty_pubkeys();
Ok(self.make_tx_keys(per_commitment_point, counterparty_points, holder_points))
}
pub(crate) fn make_holder_tx_keys(
&self,
per_commitment_point: &PublicKey,
) -> Result<TxCreationKeys, Status> {
let holder_points = self.keys.pubkeys();
let counterparty_points = self.keys.counterparty_pubkeys();
Ok(self.make_tx_keys(per_commitment_point, holder_points, counterparty_points))
}
fn make_tx_keys(
&self,
per_commitment_point: &PublicKey,
a_points: &ChannelPublicKeys,
b_points: &ChannelPublicKeys,
) -> TxCreationKeys {
TxCreationKeys::derive_new(
&self.secp_ctx,
&per_commitment_point,
&a_points.delayed_payment_basepoint,
&a_points.htlc_basepoint,
&b_points.revocation_basepoint,
&b_points.htlc_basepoint,
)
}
pub fn sign_counterparty_commitment_tx_phase2(
&mut self,
remote_per_commitment_point: &PublicKey,
commitment_number: u64,
feerate_per_kw: u32,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
) -> Result<(Signature, Vec<Signature>), Status> {
let validator = self.validator();
validator.validate_channel_value(&self.setup)?;
let info2 = self.build_counterparty_commitment_info(
to_holder_value_sat,
to_counterparty_value_sat,
offered_htlcs.clone(),
received_htlcs.clone(),
feerate_per_kw,
)?;
let node = self.get_node();
let mut state = node.get_state();
let delta =
self.enforcement_state.claimable_balances(&*state, None, Some(&info2), &self.setup);
let incoming_payment_summary =
self.enforcement_state.incoming_payments_summary(None, Some(&info2));
validator.validate_counterparty_commitment_tx(
&self.enforcement_state,
commitment_number,
&remote_per_commitment_point,
&self.setup,
&self.get_chain_state(),
&info2,
)?;
let htlcs = Self::htlcs_info2_to_oic(offered_htlcs, received_htlcs);
let commitment_tx = self.make_counterparty_commitment_tx(
remote_per_commitment_point,
commitment_number,
feerate_per_kw,
to_holder_value_sat,
to_counterparty_value_sat,
htlcs,
);
let (sig, htlc_sigs) = catch_panic!(
self.keys.sign_counterparty_commitment(&commitment_tx, Vec::new(), &self.secp_ctx),
"sign_counterparty_commitment panic {} chantype={:?}",
self.setup.commitment_type,
)
.map_err(|_| internal_error("failed to sign"))?;
let outgoing_payment_summary = self.enforcement_state.payments_summary(None, Some(&info2));
state.validate_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator.clone(),
)?;
validator.set_next_counterparty_commit_num(
&mut self.enforcement_state,
commitment_number + 1,
remote_per_commitment_point.clone(),
info2,
)?;
state.apply_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator,
);
trace_enforcement_state!(self);
self.persist()?;
Ok((sig, htlc_sigs))
}
pub(crate) fn restore_payments(&self) {
let node = self.get_node();
let incoming_payment_summary = self.enforcement_state.incoming_payments_summary(None, None);
let outgoing_payment_summary = self.enforcement_state.payments_summary(None, None);
let mut hashes: UnorderedSet<&PaymentHash> = UnorderedSet::new();
hashes.extend(incoming_payment_summary.keys());
hashes.extend(outgoing_payment_summary.keys());
let mut state = node.get_state();
for hash in hashes {
let payment = state.payments.entry(*hash).or_insert_with(|| RoutedPayment::new());
let incoming_sat = incoming_payment_summary.get(hash).map(|a| *a).unwrap_or(0);
let outgoing_sat = outgoing_payment_summary.get(hash).map(|a| *a).unwrap_or(0);
payment.apply(&self.id0, incoming_sat, outgoing_sat);
}
}
pub fn make_counterparty_commitment_tx_with_keys(
&self,
keys: TxCreationKeys,
commitment_number: u64,
feerate_per_kw: u32,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
htlcs: Vec<HTLCOutputInCommitment>,
) -> CommitmentTransaction {
let mut htlcs_with_aux = htlcs.iter().map(|h| (h.clone(), ())).collect();
let channel_parameters = self.make_channel_parameters();
let parameters = channel_parameters.as_counterparty_broadcastable();
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
INITIAL_COMMITMENT_NUMBER - commitment_number,
to_counterparty_value_sat,
to_holder_value_sat,
self.keys.counterparty_pubkeys().funding_pubkey,
self.keys.pubkeys().funding_pubkey,
keys,
feerate_per_kw,
&mut htlcs_with_aux,
¶meters,
);
commitment_tx
}
pub fn make_counterparty_commitment_tx(
&self,
remote_per_commitment_point: &PublicKey,
commitment_number: u64,
feerate_per_kw: u32,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
htlcs: Vec<HTLCOutputInCommitment>,
) -> CommitmentTransaction {
let keys = self.make_counterparty_tx_keys(remote_per_commitment_point).unwrap();
self.make_counterparty_commitment_tx_with_keys(
keys,
commitment_number,
feerate_per_kw,
to_holder_value_sat,
to_counterparty_value_sat,
htlcs,
)
}
fn check_holder_tx_signatures(
&self,
per_commitment_point: &PublicKey,
txkeys: &TxCreationKeys,
feerate_per_kw: u32,
counterparty_commit_sig: &Signature,
counterparty_htlc_sigs: &[Signature],
recomposed_tx: CommitmentTransaction,
) -> Result<(), Status> {
let redeemscript = make_funding_redeemscript(
&self.keys.pubkeys().funding_pubkey,
&self.setup.counterparty_points.funding_pubkey,
);
let sighash = Message::from_slice(
&SighashCache::new(&recomposed_tx.trust().built_transaction().transaction)
.segwit_signature_hash(
0,
&redeemscript,
self.setup.channel_value_sat,
EcdsaSighashType::All,
)
.unwrap()[..],
)
.map_err(|ve| internal_error(format!("sighash failed: {}", ve)))?;
self.secp_ctx
.verify_ecdsa(
&sighash,
&counterparty_commit_sig,
&self.setup.counterparty_points.funding_pubkey,
)
.map_err(|ve| policy_error(format!("commit sig verify failed: {}", ve)))?;
let commitment_txid = recomposed_tx.trust().txid();
let to_self_delay = self.setup.counterparty_selected_contest_delay;
let htlc_pubkey = derive_public_key(
&self.secp_ctx,
&per_commitment_point,
&self.keys.counterparty_pubkeys().htlc_basepoint,
)
.map_err(|err| internal_error(format!("derive_public_key failed: {}", err)))?;
let sig_hash_type = if self.setup.is_anchors() {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
};
let build_feerate = if self.setup.is_zero_fee_htlc() { 0 } else { feerate_per_kw };
let features = self.setup.features();
for ndx in 0..recomposed_tx.htlcs().len() {
let htlc = &recomposed_tx.htlcs()[ndx];
let htlc_redeemscript = get_htlc_redeemscript(htlc, &features, &txkeys);
let features = self.setup.features();
let recomposed_htlc_tx = catch_panic!(
build_htlc_transaction(
&commitment_txid,
build_feerate,
to_self_delay,
htlc,
&features,
&txkeys.broadcaster_delayed_payment_key,
&txkeys.revocation_key,
),
"build_htlc_transaction panic {} chantype={:?}",
self.setup.commitment_type
);
let recomposed_tx_sighash = Message::from_slice(
&SighashCache::new(&recomposed_htlc_tx)
.segwit_signature_hash(
0,
&htlc_redeemscript,
htlc.amount_msat / 1000,
sig_hash_type,
)
.unwrap()[..],
)
.map_err(|err| invalid_argument(format!("sighash failed for htlc {}: {}", ndx, err)))?;
self.secp_ctx
.verify_ecdsa(&recomposed_tx_sighash, &counterparty_htlc_sigs[ndx], &htlc_pubkey)
.map_err(|err| {
policy_error(format!("commit sig verify failed for htlc {}: {}", ndx, err))
})?;
}
Ok(())
}
fn advance_holder_commitment_state(
&mut self,
validator: Arc<dyn Validator>,
new_current_commitment_number: u64,
info2: CommitmentInfo2,
counterparty_signatures: CommitmentSignatures,
) -> Result<(PublicKey, Option<SecretKey>), Status> {
validator.set_next_holder_commit_num(
&mut self.enforcement_state,
new_current_commitment_number + 1,
info2,
counterparty_signatures,
)?;
self.revoke_previous_commitment(new_current_commitment_number)
}
fn revoke_previous_commitment(
&mut self,
commitment_number: u64,
) -> Result<(PublicKey, Option<SecretKey>), Status> {
let next_holder_commitment_point = self.get_per_commitment_point(commitment_number + 1)?;
let maybe_old_secret = if commitment_number >= 1 {
Some(self.get_per_commitment_secret(commitment_number - 1)?)
} else {
None
};
Ok((next_holder_commitment_point, maybe_old_secret))
}
pub fn validate_holder_commitment_tx_phase2(
&mut self,
commitment_number: u64,
feerate_per_kw: u32,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
counterparty_commit_sig: &Signature,
counterparty_htlc_sigs: &[Signature],
) -> Result<(), Status> {
let per_commitment_point = &self.get_per_commitment_point(commitment_number)?;
let info2 = self.build_holder_commitment_info(
to_holder_value_sat,
to_counterparty_value_sat,
offered_htlcs,
received_htlcs,
feerate_per_kw,
)?;
let node = self.get_node();
let state = node.get_state();
let delta =
self.enforcement_state.claimable_balances(&*state, Some(&info2), None, &self.setup);
let incoming_payment_summary =
self.enforcement_state.incoming_payments_summary(Some(&info2), None);
let validator = self.validator();
validator
.validate_holder_commitment_tx(
&self.enforcement_state,
commitment_number,
&per_commitment_point,
&self.setup,
&self.get_chain_state(),
&info2,
)
.map_err(|ve| {
warn!(
"VALIDATION FAILED: {}\nsetup={:#?}\nstate={:#?}\ninfo={:#?}",
ve,
&self.setup,
&self.get_chain_state(),
&info2,
);
ve
})?;
let htlcs =
Self::htlcs_info2_to_oic(info2.offered_htlcs.clone(), info2.received_htlcs.clone());
let txkeys = self.make_holder_tx_keys(&per_commitment_point).unwrap();
let recomposed_tx = self.make_holder_commitment_tx(
commitment_number,
&txkeys,
feerate_per_kw,
to_holder_value_sat,
to_counterparty_value_sat,
htlcs.clone(),
);
self.check_holder_tx_signatures(
&per_commitment_point,
&txkeys,
feerate_per_kw,
counterparty_commit_sig,
counterparty_htlc_sigs,
recomposed_tx,
)?;
let outgoing_payment_summary = self.enforcement_state.payments_summary(Some(&info2), None);
state.validate_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator.clone(),
)?;
if commitment_number == self.enforcement_state.next_holder_commit_num {
let counterparty_signatures = CommitmentSignatures(
counterparty_commit_sig.clone(),
counterparty_htlc_sigs.to_vec(),
);
self.enforcement_state.next_holder_commit_info = Some((info2, counterparty_signatures));
}
trace_enforcement_state!(self);
self.persist()?;
Ok(())
}
pub fn revoke_previous_holder_commitment(
&mut self,
new_current_commitment_number: u64,
) -> Result<(PublicKey, Option<SecretKey>), Status> {
let validator = self.validator();
let (next_holder_commitment_point, maybe_old_secret) = if let Some((info2, sigs)) =
self.enforcement_state.next_holder_commit_info.take()
{
let incoming_payment_summary =
self.enforcement_state.incoming_payments_summary(Some(&info2), None);
let outgoing_payment_summary =
self.enforcement_state.payments_summary(Some(&info2), None);
let node = self.get_node();
let mut state = node.get_state();
let delta =
self.enforcement_state.claimable_balances(&*state, Some(&info2), None, &self.setup);
let (next_holder_commitment_point, maybe_old_secret) = self
.advance_holder_commitment_state(
validator.clone(),
new_current_commitment_number,
info2,
sigs,
)?;
state.apply_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator,
);
(next_holder_commitment_point, maybe_old_secret)
} else {
self.revoke_previous_commitment(new_current_commitment_number)?
};
trace_enforcement_state!(self);
self.persist()?;
Ok((next_holder_commitment_point, maybe_old_secret))
}
pub fn sign_holder_commitment_tx_phase2(
&mut self,
commitment_number: u64,
) -> Result<(Signature, Vec<Signature>), Status> {
let validator = self.validator();
let info2 = validator
.get_current_holder_commitment_info(&mut self.enforcement_state, commitment_number)?;
let htlcs =
Self::htlcs_info2_to_oic(info2.offered_htlcs.clone(), info2.received_htlcs.clone());
let per_commitment_point = self.get_per_commitment_point(commitment_number)?;
let build_feerate = if self.setup.is_zero_fee_htlc() { 0 } else { info2.feerate_per_kw };
let txkeys = self.make_holder_tx_keys(&per_commitment_point).unwrap();
let recomposed_tx = self.make_holder_commitment_tx(
commitment_number,
&txkeys,
build_feerate,
info2.to_broadcaster_value_sat,
info2.to_countersigner_value_sat,
htlcs,
);
let htlcs_len = recomposed_tx.htlcs().len();
let mut htlc_dummy_sigs = Vec::with_capacity(htlcs_len);
htlc_dummy_sigs.resize(htlcs_len, Self::dummy_sig());
let recomposed_holder_tx = HolderCommitmentTransaction::new(
recomposed_tx,
Self::dummy_sig(),
htlc_dummy_sigs,
&self.keys.pubkeys().funding_pubkey,
&self.keys.counterparty_pubkeys().funding_pubkey,
);
let (sig, htlc_sigs) = self
.keys
.sign_holder_commitment_and_htlcs(&recomposed_holder_tx, &self.secp_ctx)
.map_err(|_| internal_error("failed to sign"))?;
self.enforcement_state.channel_closed = true;
trace_enforcement_state!(self);
self.persist()?;
Ok((sig, htlc_sigs))
}
pub fn sign_holder_commitment_tx_for_recovery(
&mut self,
) -> Result<(Transaction, Vec<Transaction>, Script, (SecretKey, Vec<Vec<u8>>), PublicKey), Status>
{
let info2 = self
.enforcement_state
.current_holder_commit_info
.as_ref()
.expect("holder commitment info should be present");
let cp_sigs = self
.enforcement_state
.current_counterparty_signatures
.as_ref()
.expect("counterparty signatures should be present");
let commitment_number = self.enforcement_state.next_holder_commit_num - 1;
warn!("force-closing channel for recovery at commitment number {}", commitment_number);
let htlcs =
Self::htlcs_info2_to_oic(info2.offered_htlcs.clone(), info2.received_htlcs.clone());
let per_commitment_point = self.get_per_commitment_point(commitment_number)?;
let build_feerate = if self.setup.is_zero_fee_htlc() { 0 } else { info2.feerate_per_kw };
let txkeys = self.make_holder_tx_keys(&per_commitment_point).unwrap();
let recomposed_tx = self.make_holder_commitment_tx(
commitment_number,
&txkeys,
build_feerate,
info2.to_broadcaster_value_sat,
info2.to_countersigner_value_sat,
htlcs,
);
let htlcs_len = recomposed_tx.htlcs().len();
let mut htlc_dummy_sigs = Vec::with_capacity(htlcs_len);
htlc_dummy_sigs.resize(htlcs_len, Self::dummy_sig());
let recomposed_holder_tx = HolderCommitmentTransaction::new(
recomposed_tx,
Self::dummy_sig(),
htlc_dummy_sigs,
&self.keys.pubkeys().funding_pubkey,
&self.keys.counterparty_pubkeys().funding_pubkey,
);
let (sig, _htlc_sigs) = self
.keys
.sign_holder_commitment_and_htlcs(&recomposed_holder_tx, &self.secp_ctx)
.map_err(|_| internal_error("failed to sign"))?;
let holder_tx = recomposed_holder_tx.trust();
let mut tx = holder_tx.built_transaction().transaction.clone();
let holder_funding_key = self.keys.pubkeys().funding_pubkey;
let counterparty_funding_key = self.keys.counterparty_pubkeys().funding_pubkey;
let tx_keys = holder_tx.keys();
let revocable_redeemscript = chan_utils::get_revokeable_redeemscript(
&tx_keys.revocation_key,
self.setup.counterparty_selected_contest_delay,
&tx_keys.broadcaster_delayed_payment_key,
);
add_holder_sig(&mut tx, sig, cp_sigs.0, &holder_funding_key, &counterparty_funding_key);
self.enforcement_state.channel_closed = true;
trace_enforcement_state!(self);
let revocation_basepoint = self.keys.counterparty_pubkeys().revocation_basepoint;
let revocation_pubkey = derive_public_revocation_key(
&self.secp_ctx,
&per_commitment_point,
&revocation_basepoint,
);
let ck =
self.get_unilateral_close_key(&Some(per_commitment_point), &Some(revocation_pubkey))?;
self.persist()?;
Ok((tx, Vec::new(), revocable_redeemscript.to_v0_p2wsh(), ck, revocation_pubkey))
}
pub fn sign_holder_commitment_tx_phase2_redundant(
&mut self,
commitment_number: u64,
feerate_per_kw: u32,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
) -> Result<(Signature, Vec<Signature>), Status> {
let per_commitment_point = self.get_per_commitment_point(commitment_number)?;
let info2 = self.build_holder_commitment_info(
to_holder_value_sat,
to_counterparty_value_sat,
offered_htlcs.clone(),
received_htlcs.clone(),
feerate_per_kw,
)?;
self.validator().validate_holder_commitment_tx(
&self.enforcement_state,
commitment_number,
&per_commitment_point,
&self.setup,
&self.get_chain_state(),
&info2,
)?;
let htlcs = Self::htlcs_info2_to_oic(offered_htlcs, received_htlcs);
let mut htlc_dummy_sigs = Vec::with_capacity(htlcs.len());
htlc_dummy_sigs.resize(htlcs.len(), Self::dummy_sig());
let build_feerate = if self.setup.is_zero_fee_htlc() { 0 } else { feerate_per_kw };
let txkeys = self.make_holder_tx_keys(&per_commitment_point).unwrap();
let commitment_tx = self.make_holder_commitment_tx(
commitment_number,
&txkeys,
build_feerate,
to_holder_value_sat,
to_counterparty_value_sat,
htlcs,
);
debug!("channel: sign holder txid {}", commitment_tx.trust().built_transaction().txid);
let holder_commitment_tx = HolderCommitmentTransaction::new(
commitment_tx,
Self::dummy_sig(),
htlc_dummy_sigs,
&self.keys.pubkeys().funding_pubkey,
&self.keys.counterparty_pubkeys().funding_pubkey,
);
let (sig, htlc_sigs) = self
.keys
.sign_holder_commitment_and_htlcs(&holder_commitment_tx, &self.secp_ctx)
.map_err(|_| internal_error("failed to sign"))?;
self.enforcement_state.channel_closed = true;
trace_enforcement_state!(self);
self.persist()?;
Ok((sig, htlc_sigs))
}
pub(crate) fn make_holder_commitment_tx(
&self,
commitment_number: u64,
keys: &TxCreationKeys,
feerate_per_kw: u32,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
htlcs: Vec<HTLCOutputInCommitment>,
) -> CommitmentTransaction {
let mut htlcs_with_aux = htlcs.iter().map(|h| (h.clone(), ())).collect();
let channel_parameters = self.make_channel_parameters();
let parameters = channel_parameters.as_holder_broadcastable();
let mut commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
INITIAL_COMMITMENT_NUMBER - commitment_number,
to_holder_value_sat,
to_counterparty_value_sat,
self.keys.pubkeys().funding_pubkey,
self.keys.counterparty_pubkeys().funding_pubkey,
keys.clone(),
feerate_per_kw,
&mut htlcs_with_aux,
¶meters,
);
if self.setup.is_anchors() {
commitment_tx = commitment_tx.with_non_zero_fee_anchors();
}
commitment_tx
}
pub fn htlcs_info2_to_oic(
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
) -> Vec<HTLCOutputInCommitment> {
let mut htlcs = Vec::new();
for htlc in offered_htlcs {
htlcs.push(HTLCOutputInCommitment {
offered: true,
amount_msat: htlc.value_sat * 1000,
cltv_expiry: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
transaction_output_index: None,
});
}
for htlc in received_htlcs {
htlcs.push(HTLCOutputInCommitment {
offered: false,
amount_msat: htlc.value_sat * 1000,
cltv_expiry: htlc.cltv_expiry,
payment_hash: htlc.payment_hash,
transaction_output_index: None,
});
}
htlcs
}
pub fn make_channel_parameters(&self) -> ChannelTransactionParameters {
let funding_outpoint = chain::transaction::OutPoint {
txid: self.setup.funding_outpoint.txid,
index: self.setup.funding_outpoint.vout as u16,
};
let channel_parameters = ChannelTransactionParameters {
holder_pubkeys: self.get_channel_basepoints(),
holder_selected_contest_delay: self.setup.holder_selected_contest_delay,
is_outbound_from_holder: self.setup.is_outbound,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
pubkeys: self.setup.counterparty_points.clone(),
selected_contest_delay: self.setup.counterparty_selected_contest_delay,
}),
funding_outpoint: Some(funding_outpoint),
channel_type_features: self.setup.features(),
};
channel_parameters
}
pub fn get_ldk_shutdown_script(&self) -> Script {
self.setup.holder_shutdown_script.clone().unwrap_or_else(|| {
self.get_node().keys_manager.get_shutdown_scriptpubkey().unwrap().into()
})
}
fn get_node(&self) -> Arc<Node> {
self.node.upgrade().unwrap()
}
pub fn sign_mutual_close_tx_phase2(
&mut self,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
holder_script: &Option<Script>,
counterparty_script: &Option<Script>,
holder_wallet_path_hint: &[u32],
) -> Result<Signature, Status> {
self.validator().validate_mutual_close_tx(
&*self.get_node(),
&self.setup,
&self.enforcement_state,
to_holder_value_sat,
to_counterparty_value_sat,
holder_script,
counterparty_script,
holder_wallet_path_hint,
)?;
let tx = ClosingTransaction::new(
to_holder_value_sat,
to_counterparty_value_sat,
holder_script.clone().unwrap_or_else(|| Script::new()),
counterparty_script.clone().unwrap_or_else(|| Script::new()),
self.setup.funding_outpoint,
);
let sig = self
.keys
.sign_closing_transaction(&tx, &self.secp_ctx)
.map_err(|_| Status::internal("failed to sign"))?;
self.enforcement_state.channel_closed = true;
trace_enforcement_state!(self);
self.persist()?;
Ok(sig)
}
pub fn sign_delayed_sweep(
&self,
tx: &Transaction,
input: usize,
commitment_number: u64,
redeemscript: &Script,
amount_sat: u64,
wallet_path: &[u32],
) -> Result<Signature, Status> {
if input >= tx.input.len() {
return Err(invalid_argument(format!(
"sign_delayed_sweep: bad input index: {} >= {}",
input,
tx.input.len()
)));
}
let per_commitment_point = self.get_per_commitment_point(commitment_number)?;
self.validator().validate_delayed_sweep(
&*self.get_node(),
&self.setup,
&self.get_chain_state(),
tx,
input,
amount_sat,
wallet_path,
)?;
let sighash = Message::from_slice(
&SighashCache::new(tx)
.segwit_signature_hash(input, &redeemscript, amount_sat, EcdsaSighashType::All)
.unwrap()[..],
)
.map_err(|_| Status::internal("failed to sighash"))?;
let privkey = derive_private_key(
&self.secp_ctx,
&per_commitment_point,
&self.keys.delayed_payment_base_key,
);
let sig = self.secp_ctx.sign_ecdsa(&sighash, &privkey);
trace_enforcement_state!(self);
Ok(sig)
}
pub fn sign_counterparty_htlc_sweep(
&self,
tx: &Transaction,
input: usize,
remote_per_commitment_point: &PublicKey,
redeemscript: &Script,
htlc_amount_sat: u64,
wallet_path: &[u32],
) -> Result<Signature, Status> {
if input >= tx.input.len() {
return Err(invalid_argument(format!(
"sign_counterparty_htlc_sweep: bad input index: {} >= {}",
input,
tx.input.len()
)));
}
self.validator().validate_counterparty_htlc_sweep(
&*self.get_node(),
&self.setup,
&self.get_chain_state(),
tx,
redeemscript,
input,
htlc_amount_sat,
wallet_path,
)?;
let htlc_sighash = Message::from_slice(
&SighashCache::new(tx)
.segwit_signature_hash(input, &redeemscript, htlc_amount_sat, EcdsaSighashType::All)
.unwrap()[..],
)
.map_err(|_| Status::internal("failed to sighash"))?;
let htlc_privkey = derive_private_key(
&self.secp_ctx,
&remote_per_commitment_point,
&self.keys.htlc_base_key,
);
let sig = self.secp_ctx.sign_ecdsa(&htlc_sighash, &htlc_privkey);
trace_enforcement_state!(self);
Ok(sig)
}
pub fn sign_justice_sweep(
&self,
tx: &Transaction,
input: usize,
revocation_secret: &SecretKey,
redeemscript: &Script,
amount_sat: u64,
wallet_path: &[u32],
) -> Result<Signature, Status> {
if input >= tx.input.len() {
return Err(invalid_argument(format!(
"sign_justice_sweep: bad input index: {} >= {}",
input,
tx.input.len()
)));
}
self.validator().validate_justice_sweep(
&*self.get_node(),
&self.setup,
&self.get_chain_state(),
tx,
input,
amount_sat,
wallet_path,
)?;
let sighash = Message::from_slice(
&SighashCache::new(tx)
.segwit_signature_hash(input, &redeemscript, amount_sat, EcdsaSighashType::All)
.unwrap()[..],
)
.map_err(|_| Status::internal("failed to sighash"))?;
let privkey = chan_utils::derive_private_revocation_key(
&self.secp_ctx,
revocation_secret,
&self.keys.revocation_base_key,
);
let sig = self.secp_ctx.sign_ecdsa(&sighash, &privkey);
trace_enforcement_state!(self);
Ok(sig)
}
pub fn sign_channel_announcement_with_funding_key(&self, announcement: &[u8]) -> Signature {
let ann_hash = Sha256dHash::hash(announcement);
let encmsg = secp256k1::Message::from_slice(&ann_hash[..]).expect("encmsg failed");
self.secp_ctx.sign_ecdsa(&encmsg, &self.keys.funding_key)
}
fn persist(&self) -> Result<(), Status> {
let node_id = self.get_node().get_id();
self.get_node()
.persister
.update_channel(&node_id, &self)
.map_err(|_| Status::internal("persist failed"))
}
pub fn network(&self) -> Network {
self.get_node().network()
}
pub fn funding_signed(&self, tx: &Transaction, _vout: u32) {
self.monitor.add_funding_inputs(tx);
}
pub fn forget(&self) -> Result<(), Status> {
self.monitor.forget_channel();
self.persist()?;
Ok(())
}
pub fn balance(&self) -> ChannelBalance {
let node = self.get_node();
let state = node.get_state();
self.enforcement_state.balance(&*state, &self.setup)
}
#[cfg(feature = "test_utils")]
pub fn advance_holder_commitment(
&mut self,
counterparty_key: &SecretKey,
counterparty_htlc_key: &SecretKey,
offered_htlcs: Vec<HTLCInfo2>,
value_to_holder: u64,
commit_num: u64,
) -> Result<(), Status> {
let feerate = 1000;
let funding_redeemscript = make_funding_redeemscript(
&self.keys.pubkeys().funding_pubkey,
&self.keys.counterparty_pubkeys().funding_pubkey,
);
let per_commitment_point = self.get_per_commitment_point(commit_num)?;
let txkeys = self.make_holder_tx_keys(&per_commitment_point).unwrap();
let tx = self.make_holder_commitment_tx(
commit_num,
&txkeys,
feerate,
value_to_holder,
0,
Channel::htlcs_info2_to_oic(offered_htlcs.clone(), vec![]),
);
let trusted_tx = tx.trust();
let built_tx = trusted_tx.built_transaction();
let counterparty_sig = built_tx.sign_counterparty_commitment(
&counterparty_key,
&funding_redeemscript,
self.setup.channel_value_sat,
&self.secp_ctx,
);
let counterparty_htlc_key =
derive_private_key(&self.secp_ctx, &per_commitment_point, &counterparty_htlc_key);
let features = self.setup.features();
let mut htlc_sigs = Vec::with_capacity(tx.htlcs().len());
for htlc in tx.htlcs() {
let htlc_tx = catch_panic!(
build_htlc_transaction(
&trusted_tx.txid(),
feerate,
self.setup.counterparty_selected_contest_delay,
htlc,
&features,
&txkeys.broadcaster_delayed_payment_key,
&txkeys.revocation_key,
),
"build_htlc_transaction panic {} chantype={:?} htlc={:?}",
self.setup.commitment_type,
htlc
);
let htlc_redeemscript = get_htlc_redeemscript(&htlc, &features, &txkeys);
let sig_hash_type = if self.setup.is_anchors() {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
};
let htlc_sighash = Message::from_slice(
&SighashCache::new(&htlc_tx)
.segwit_signature_hash(
0,
&htlc_redeemscript,
htlc.amount_msat / 1000,
sig_hash_type,
)
.unwrap()[..],
)
.unwrap();
htlc_sigs.push(self.secp_ctx.sign_ecdsa(&htlc_sighash, &counterparty_htlc_key));
}
self.validate_holder_commitment_tx_phase2(
commit_num,
feerate,
value_to_holder,
0,
offered_htlcs,
vec![],
&counterparty_sig,
&htlc_sigs,
)?;
self.revoke_previous_holder_commitment(commit_num)?;
Ok(())
}
pub fn sign_holder_anchor_input(
&self,
anchor_tx: &Transaction,
input: usize,
) -> Result<Signature, Status> {
let witness_script = self.get_anchor_redeemscript();
let sighash = Message::from_slice(
&SighashCache::new(&*anchor_tx)
.segwit_signature_hash(
input,
&witness_script,
ANCHOR_OUTPUT_VALUE_SATOSHI,
EcdsaSighashType::All,
)
.unwrap()[..],
)
.map_err(|ve| internal_error(format!("sighash failed: {}", ve)))?;
Ok(self.secp_ctx.sign_ecdsa_with_noncedata(
&sighash,
&self.keys.funding_key,
&self.keys.get_secure_random_bytes(),
))
}
pub fn get_anchor_redeemscript(&self) -> Script {
chan_utils::get_anchor_redeemscript(&self.keys.pubkeys().funding_pubkey)
}
}
#[derive(Debug, PartialEq)]
pub struct ChannelBalance {
pub claimable: u64,
pub received_htlc: u64,
pub offered_htlc: u64,
pub sweeping: u64,
pub channel_count: u32,
pub received_htlc_count: u32,
pub offered_htlc_count: u32,
}
impl ChannelBalance {
pub fn new(
claimable: u64,
received_htlc: u64,
offered_htlc: u64,
sweeping: u64,
channel_count: u32,
received_htlc_count: u32,
offered_htlc_count: u32,
) -> ChannelBalance {
ChannelBalance {
claimable,
received_htlc,
offered_htlc,
sweeping,
channel_count,
received_htlc_count,
offered_htlc_count,
}
}
pub fn zero() -> ChannelBalance {
ChannelBalance {
claimable: 0,
received_htlc: 0,
offered_htlc: 0,
sweeping: 0,
channel_count: 0,
received_htlc_count: 0,
offered_htlc_count: 0,
}
}
pub fn accumulate(&mut self, other: &ChannelBalance) {
self.claimable += other.claimable;
self.received_htlc += other.received_htlc;
self.offered_htlc += other.offered_htlc;
self.sweeping += other.sweeping;
self.channel_count += other.channel_count;
self.received_htlc_count += other.received_htlc_count;
self.offered_htlc_count += other.offered_htlc_count;
}
}
impl Channel {
pub(crate) fn build_counterparty_commitment_info(
&self,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
feerate_per_kw: u32,
) -> Result<CommitmentInfo2, Status> {
Ok(CommitmentInfo2::new(
true,
to_holder_value_sat,
to_counterparty_value_sat,
offered_htlcs,
received_htlcs,
feerate_per_kw,
))
}
fn build_holder_commitment_info(
&self,
to_holder_value_sat: u64,
to_counterparty_value_sat: u64,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
feerate_per_kw: u32,
) -> Result<CommitmentInfo2, Status> {
Ok(CommitmentInfo2::new(
false,
to_counterparty_value_sat,
to_holder_value_sat,
offered_htlcs,
received_htlcs,
feerate_per_kw,
))
}
pub fn sign_counterparty_commitment_tx(
&mut self,
tx: &Transaction,
output_witscripts: &[Vec<u8>],
remote_per_commitment_point: &PublicKey,
commitment_number: u64,
feerate_per_kw: u32,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
) -> Result<Signature, Status> {
if tx.output.len() != output_witscripts.len() {
return Err(invalid_argument("len(tx.output) != len(witscripts)"));
}
let validator = self.validator();
validator.validate_channel_value(&self.setup)?;
let is_counterparty = true;
let info = validator.decode_commitment_tx(
&self.keys,
&self.setup,
is_counterparty,
tx,
output_witscripts,
)?;
let info2 = self.build_counterparty_commitment_info(
info.to_countersigner_value_sat,
info.to_broadcaster_value_sat,
offered_htlcs,
received_htlcs,
feerate_per_kw,
)?;
let node = self.get_node();
let mut state = node.get_state();
let delta =
self.enforcement_state.claimable_balances(&*state, None, Some(&info2), &self.setup);
let incoming_payment_summary =
self.enforcement_state.incoming_payments_summary(None, Some(&info2));
validator
.validate_counterparty_commitment_tx(
&self.enforcement_state,
commitment_number,
&remote_per_commitment_point,
&self.setup,
&self.get_chain_state(),
&info2,
)
.map_err(|ve| {
debug!(
"VALIDATION FAILED: {}\ntx={:#?}\nsetup={:#?}\ncstate={:#?}\ninfo={:#?}",
ve,
&tx,
&self.setup,
&self.get_chain_state(),
&info2,
);
ve
})?;
let htlcs =
Self::htlcs_info2_to_oic(info2.offered_htlcs.clone(), info2.received_htlcs.clone());
let recomposed_tx = self.make_counterparty_commitment_tx(
remote_per_commitment_point,
commitment_number,
feerate_per_kw,
info2.to_countersigner_value_sat,
info2.to_broadcaster_value_sat,
htlcs,
);
if recomposed_tx.trust().built_transaction().transaction != *tx {
debug!("ORIGINAL_TX={:#?}", &tx);
debug!("RECOMPOSED_TX={:#?}", &recomposed_tx.trust().built_transaction().transaction);
policy_err!(validator, "policy-commitment", "recomposed tx mismatch");
}
let commit_num = INITIAL_COMMITMENT_NUMBER - recomposed_tx.trust().commitment_number();
let point = recomposed_tx.trust().keys().per_commitment_point;
let trusted_tx = recomposed_tx.trust();
let funding_pubkey = &self.keys.pubkeys().funding_pubkey;
let counterparty_funding_pubkey = &self.setup.counterparty_points.funding_pubkey;
let channel_funding_redeemscript =
make_funding_redeemscript(funding_pubkey, counterparty_funding_pubkey);
let built_tx = trusted_tx.built_transaction();
let sig = catch_panic!(
built_tx.sign_counterparty_commitment(
&self.keys.funding_key,
&channel_funding_redeemscript,
self.setup.channel_value_sat,
&self.secp_ctx,
),
"sign_counterparty_commitment panic {} chantype={:?}",
self.setup.commitment_type
);
let outgoing_payment_summary = self.enforcement_state.payments_summary(None, Some(&info2));
state.validate_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator.clone(),
)?;
validator.set_next_counterparty_commit_num(
&mut self.enforcement_state,
commit_num + 1,
point,
info2,
)?;
state.apply_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator,
);
trace_enforcement_state!(self);
self.persist()?;
Ok(sig)
}
fn make_validated_recomposed_holder_commitment_tx(
&self,
tx: &Transaction,
output_witscripts: &[Vec<u8>],
commitment_number: u64,
per_commitment_point: PublicKey,
txkeys: &TxCreationKeys,
feerate_per_kw: u32,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
) -> Result<(CommitmentTransaction, CommitmentInfo2, Map<PaymentHash, u64>), Status> {
if tx.output.len() != output_witscripts.len() {
return Err(invalid_argument(format!(
"len(tx.output):{} != len(witscripts):{}",
tx.output.len(),
output_witscripts.len()
)));
}
let validator = self.validator();
validator.validate_channel_value(&self.setup)?;
let is_counterparty = false;
let info = validator.decode_commitment_tx(
&self.keys,
&self.setup,
is_counterparty,
tx,
output_witscripts,
)?;
let info2 = self.build_holder_commitment_info(
info.to_broadcaster_value_sat,
info.to_countersigner_value_sat,
offered_htlcs.clone(),
received_htlcs.clone(),
feerate_per_kw,
)?;
let incoming_payment_summary =
self.enforcement_state.incoming_payments_summary(Some(&info2), None);
validator
.validate_holder_commitment_tx(
&self.enforcement_state,
commitment_number,
&per_commitment_point,
&self.setup,
&self.get_chain_state(),
&info2,
)
.map_err(|ve| {
warn!(
"VALIDATION FAILED: {}\ntx={:#?}\nsetup={:#?}\nstate={:#?}\ninfo={:#?}",
ve,
&tx,
&self.setup,
&self.get_chain_state(),
&info2,
);
ve
})?;
let htlcs =
Self::htlcs_info2_to_oic(info2.offered_htlcs.clone(), info2.received_htlcs.clone());
let recomposed_tx = self.make_holder_commitment_tx(
commitment_number,
txkeys,
feerate_per_kw,
info.to_broadcaster_value_sat,
info.to_countersigner_value_sat,
htlcs.clone(),
);
if recomposed_tx.trust().built_transaction().transaction != *tx {
dbgvals!(
&self.setup,
&self.enforcement_state,
tx,
DebugVecVecU8(output_witscripts),
commitment_number,
feerate_per_kw,
&offered_htlcs,
&received_htlcs
);
warn!("RECOMPOSITION FAILED");
warn!("ORIGINAL_TX={:#?}", &tx);
warn!("RECOMPOSED_TX={:#?}", &recomposed_tx.trust().built_transaction().transaction);
policy_err!(validator, "policy-commitment", "recomposed tx mismatch");
}
Ok((recomposed_tx, info2, incoming_payment_summary))
}
pub fn validate_holder_commitment_tx(
&mut self,
tx: &Transaction,
output_witscripts: &[Vec<u8>],
commitment_number: u64,
feerate_per_kw: u32,
offered_htlcs: Vec<HTLCInfo2>,
received_htlcs: Vec<HTLCInfo2>,
counterparty_commit_sig: &Signature,
counterparty_htlc_sigs: &[Signature],
) -> Result<(), Status> {
let validator = self.validator();
let per_commitment_point = self.get_per_commitment_point(commitment_number)?;
let txkeys = self
.make_holder_tx_keys(&per_commitment_point)
.map_err(|err| internal_error(format!("make_holder_tx_keys failed: {}", err)))?;
let (recomposed_tx, info2, incoming_payment_summary) = self
.make_validated_recomposed_holder_commitment_tx(
tx,
output_witscripts,
commitment_number,
per_commitment_point,
&txkeys,
feerate_per_kw,
offered_htlcs,
received_htlcs,
)?;
let node = self.get_node();
let state = node.get_state();
let delta =
self.enforcement_state.claimable_balances(&*state, Some(&info2), None, &self.setup);
self.check_holder_tx_signatures(
&per_commitment_point,
&txkeys,
feerate_per_kw,
counterparty_commit_sig,
counterparty_htlc_sigs,
recomposed_tx,
)?;
let outgoing_payment_summary = self.enforcement_state.payments_summary(Some(&info2), None);
state.validate_payments(
&self.id0,
&incoming_payment_summary,
&outgoing_payment_summary,
&delta,
validator.clone(),
)?;
if commitment_number == self.enforcement_state.next_holder_commit_num {
let counterparty_signatures = CommitmentSignatures(
counterparty_commit_sig.clone(),
counterparty_htlc_sigs.to_vec(),
);
self.enforcement_state.next_holder_commit_info = Some((info2, counterparty_signatures));
}
trace_enforcement_state!(self);
self.persist()?;
Ok(())
}
pub fn activate_initial_commitment(&mut self) -> Result<PublicKey, Status> {
debug!("activate_initial_commitment");
if self.enforcement_state.next_holder_commit_num != 0 {
return Err(invalid_argument(format!(
"activate_initial_commitment called with next_holder_commit_num {}",
self.enforcement_state.next_holder_commit_num
)));
}
if let Some((info2, sigs)) = self.enforcement_state.next_holder_commit_info.take() {
self.enforcement_state.set_next_holder_commit_num(1, info2, sigs);
} else {
return Err(invalid_argument(format!(
"activate_initial_commitment called before validation of the initial commitment"
)));
}
trace_enforcement_state!(self);
self.persist()?;
Ok(self.get_per_commitment_point_unchecked(1))
}
pub fn validate_counterparty_revocation(
&mut self,
revoke_num: u64,
old_secret: &SecretKey,
) -> Result<(), Status> {
let validator = self.validator();
validator.validate_counterparty_revocation(
&self.enforcement_state,
revoke_num,
old_secret,
)?;
if let Some(secrets) = self.enforcement_state.counterparty_secrets.as_mut() {
let backwards_num = INITIAL_COMMITMENT_NUMBER - revoke_num;
if secrets.provide_secret(backwards_num, old_secret.secret_bytes()).is_err() {
error!(
"secret does not chain: {} ({}) {} into {:?}",
revoke_num,
backwards_num,
old_secret.display_secret(),
secrets
);
policy_err!(
validator,
"policy-commitment-previous-revoked",
"counterparty secret does not chain"
)
}
}
validator.set_next_counterparty_revoke_num(&mut self.enforcement_state, revoke_num + 1)?;
trace_enforcement_state!(self);
self.persist()?;
Ok(())
}
pub fn sign_mutual_close_tx(
&mut self,
tx: &Transaction,
opaths: &[Vec<u32>],
) -> Result<Signature, Status> {
dbgvals!(tx.txid(), self.get_node().allowlist().unwrap());
if opaths.len() != tx.output.len() {
return Err(invalid_argument(format!(
"{}: bad opath len {} with tx.output len {}",
short_function!(),
opaths.len(),
tx.output.len()
)));
}
let recomposed_tx = self.validator().decode_and_validate_mutual_close_tx(
&*self.get_node(),
&self.setup,
&self.enforcement_state,
tx,
opaths,
)?;
let sig = self
.keys
.sign_closing_transaction(&recomposed_tx, &self.secp_ctx)
.map_err(|_| Status::internal("failed to sign"))?;
self.enforcement_state.channel_closed = true;
trace_enforcement_state!(self);
self.persist()?;
Ok(sig)
}
pub fn sign_holder_htlc_tx(
&self,
tx: &Transaction,
commitment_number: u64,
opt_per_commitment_point: Option<PublicKey>,
redeemscript: &Script,
htlc_amount_sat: u64,
output_witscript: &Script,
) -> Result<TypedSignature, Status> {
let per_commitment_point = if opt_per_commitment_point.is_some() {
opt_per_commitment_point.unwrap()
} else {
self.get_per_commitment_point(commitment_number)?
};
let txkeys =
self.make_holder_tx_keys(&per_commitment_point).expect("failed to make txkeys");
self.sign_htlc_tx(
tx,
&per_commitment_point,
redeemscript,
htlc_amount_sat,
output_witscript,
false, txkeys,
)
}
pub fn sign_counterparty_htlc_tx(
&self,
tx: &Transaction,
remote_per_commitment_point: &PublicKey,
redeemscript: &Script,
htlc_amount_sat: u64,
output_witscript: &Script,
) -> Result<TypedSignature, Status> {
let txkeys = self
.make_counterparty_tx_keys(&remote_per_commitment_point)
.expect("failed to make txkeys");
self.sign_htlc_tx(
tx,
remote_per_commitment_point,
redeemscript,
htlc_amount_sat,
output_witscript,
true, txkeys,
)
}
pub fn sign_htlc_tx(
&self,
tx: &Transaction,
per_commitment_point: &PublicKey,
redeemscript: &Script,
htlc_amount_sat: u64,
output_witscript: &Script,
is_counterparty: bool,
txkeys: TxCreationKeys,
) -> Result<TypedSignature, Status> {
let (feerate_per_kw, htlc, recomposed_tx_sighash, sighash_type) =
self.validator().decode_and_validate_htlc_tx(
is_counterparty,
&self.setup,
&txkeys,
tx,
&redeemscript,
htlc_amount_sat,
output_witscript,
)?;
self.validator()
.validate_htlc_tx(
&self.setup,
&self.get_chain_state(),
is_counterparty,
&htlc,
feerate_per_kw,
)
.map_err(|ve| {
debug!(
"VALIDATION FAILED: {}\n\
setup={:#?}\n\
state={:#?}\n\
is_counterparty={}\n\
tx={:#?}\n\
htlc={:#?}\n\
feerate_per_kw={}",
ve,
&self.setup,
&self.get_chain_state(),
is_counterparty,
&tx,
DebugHTLCOutputInCommitment(&htlc),
feerate_per_kw,
);
ve
})?;
let htlc_privkey =
derive_private_key(&self.secp_ctx, &per_commitment_point, &self.keys.htlc_base_key);
let htlc_sighash = Message::from_slice(&recomposed_tx_sighash[..])
.map_err(|_| Status::internal("failed to sighash recomposed"))?;
Ok(TypedSignature {
sig: self.secp_ctx.sign_ecdsa(&htlc_sighash, &htlc_privkey),
typ: sighash_type,
})
}
pub fn get_unilateral_close_key(
&self,
commitment_point: &Option<PublicKey>,
revocation_pubkey: &Option<PublicKey>,
) -> Result<(SecretKey, Vec<Vec<u8>>), Status> {
if let Some(commitment_point) = commitment_point {
let base_key = if revocation_pubkey.is_some() {
&self.keys.delayed_payment_base_key
} else {
&self.keys.payment_key
};
let key = derive_private_key(&self.secp_ctx, &commitment_point, base_key);
let pubkey = PublicKey::from_secret_key(&self.secp_ctx, &key);
let witness_stack_prefix = if let Some(r) = revocation_pubkey {
let contest_delay = self.setup.counterparty_selected_contest_delay;
let redeemscript =
chan_utils::get_revokeable_redeemscript(r, contest_delay, &pubkey).to_bytes();
vec![vec![], redeemscript]
} else {
return Err(invalid_argument(
"no support for legacy rotated to-remote, commitment point is provided and revocation_pubkey is not"
));
};
Ok((key, witness_stack_prefix))
} else {
if revocation_pubkey.is_some() {
return Err(invalid_argument(
"delayed to-local output must be rotated, but no commitment point provided",
));
}
let key = self.keys.payment_key.clone();
let pubkey = PublicKey::from_secret_key(&self.secp_ctx, &key);
let witness_stack_prefix = if self.setup.is_anchors() {
let redeemscript =
chan_utils::get_to_countersignatory_with_anchors_redeemscript(&pubkey)
.to_bytes();
vec![redeemscript]
} else {
vec![pubkey.serialize().to_vec()]
};
Ok((key, witness_stack_prefix))
}
}
pub fn htlcs_fulfilled(&mut self, preimages: Vec<PaymentPreimage>) {
let validator = self.validator();
let node = self.get_node();
node.htlcs_fulfilled(&self.id0, preimages, validator);
}
fn dummy_sig() -> Signature {
Signature::from_compact(&Vec::from_hex("eb299947b140c0e902243ee839ca58c71291f4cce49ac0367fb4617c4b6e890f18bc08b9be6726c090af4c6b49b2277e134b34078f710a72a5752e39f0139149").unwrap()).unwrap()
}
}
#[derive(Clone)]
pub(crate) struct ChannelCommitmentPointProvider {
chan: Arc<Mutex<ChannelSlot>>,
}
impl ChannelCommitmentPointProvider {
pub(crate) fn new(chan: Arc<Mutex<ChannelSlot>>) -> Self {
match &*chan.lock().unwrap() {
ChannelSlot::Stub(_) => panic!("unexpected stub"),
ChannelSlot::Ready(_) => {}
}
Self { chan }
}
}
impl SendSync for ChannelCommitmentPointProvider {}
impl CommitmentPointProvider for ChannelCommitmentPointProvider {
fn get_holder_commitment_point(&self, commitment_number: u64) -> PublicKey {
let slot = self.chan.lock().unwrap();
let chan = match &*slot {
ChannelSlot::Stub(_) => panic!("unexpected stub"),
ChannelSlot::Ready(c) => c,
};
chan.get_per_commitment_point_unchecked(commitment_number)
}
fn get_counterparty_commitment_point(&self, commitment_number: u64) -> Option<PublicKey> {
let slot = self.chan.lock().unwrap();
let chan = match &*slot {
ChannelSlot::Stub(_) => panic!("unexpected stub"),
ChannelSlot::Ready(c) => c,
};
chan.get_counterparty_commitment_point(commitment_number)
}
fn get_transaction_parameters(&self) -> ChannelTransactionParameters {
let slot = self.chan.lock().unwrap();
let chan = match &*slot {
ChannelSlot::Stub(_) => panic!("unexpected stub"),
ChannelSlot::Ready(c) => c,
};
chan.make_channel_parameters()
}
fn clone_box(&self) -> Box<dyn CommitmentPointProvider> {
Box::new(ChannelCommitmentPointProvider { chan: self.chan.clone() })
}
}
#[cfg(test)]
mod tests {
use bitcoin::hashes::hex::ToHex;
use bitcoin::psbt::serialize::Serialize;
use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
use lightning::ln::chan_utils::HTLCOutputInCommitment;
use lightning::ln::PaymentHash;
use crate::channel::ChannelBase;
use crate::util::test_utils::{
init_node_and_channel, make_test_channel_setup, TEST_NODE_CONFIG, TEST_SEED,
};
#[test]
fn test_dummy_sig() {
let dummy_sig = Secp256k1::new().sign_ecdsa(
&secp256k1::Message::from_slice(&[42; 32]).unwrap(),
&SecretKey::from_slice(&[42; 32]).unwrap(),
);
let ser = dummy_sig.serialize_compact();
assert_eq!("eb299947b140c0e902243ee839ca58c71291f4cce49ac0367fb4617c4b6e890f18bc08b9be6726c090af4c6b49b2277e134b34078f710a72a5752e39f0139149", ser.to_hex());
}
#[test]
fn tx_size_test() {
let (node, channel_id) =
init_node_and_channel(TEST_NODE_CONFIG, TEST_SEED[1], make_test_channel_setup());
node.with_channel(&channel_id, |chan| {
let n = 1;
let commitment_point = chan.get_per_commitment_point(n).unwrap();
let txkeys = chan.make_holder_tx_keys(&commitment_point).unwrap();
let htlcs = (0..583)
.map(|i| HTLCOutputInCommitment {
offered: true,
amount_msat: 1000000,
cltv_expiry: 100,
payment_hash: PaymentHash([0; 32]),
transaction_output_index: Some(i),
})
.collect();
let tx = chan.make_holder_commitment_tx(n, &txkeys, 1, 1, 1, htlcs);
let tx_size = tx.trust().built_transaction().transaction.serialize().len();
assert_eq!(tx_size, 25196);
Ok(())
})
.unwrap();
}
}