Skip to content

Commit 06f5930

Browse files
committed
Let ChannelSigner set to_remote scriptpubkey
This allows the `to_remote` output to easily be changed according to the features of the channel, or the evolution of the LN specification. `to_remote` could even be set to completely arbitrary scripts if compatibility with the formal LN spec is not required. Builders of `CommitmentTransaction` now ask a `ChannelSigner` for the appropriate `to_remote` script to use, and then pass the script to the `CommitmentTransaction` constructor. External signers now provide the expected `to_remote` script to the `verify` call of `CommitmentTransaction`.
1 parent abf72a5 commit 06f5930

File tree

6 files changed

+105
-43
lines changed

6 files changed

+105
-43
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,19 +1331,19 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
13311331
ChannelMonitor { inner: Mutex::new(imp) }
13321332
}
13331333

1334-
pub(crate) fn new(secp_ctx: Secp256k1<secp256k1::All>, keys: Signer, shutdown_script: Option<ScriptBuf>,
1334+
pub(crate) fn new(secp_ctx: Secp256k1<secp256k1::All>, mut keys: Signer, shutdown_script: Option<ScriptBuf>,
13351335
on_counterparty_tx_csv: u16, destination_script: &Script, funding_info: (OutPoint, ScriptBuf),
13361336
channel_parameters: &ChannelTransactionParameters, holder_pays_commitment_tx_fee: bool,
13371337
funding_redeemscript: ScriptBuf, channel_value_satoshis: u64,
13381338
commitment_transaction_number_obscure_factor: u64,
13391339
initial_holder_commitment_tx: HolderCommitmentTransaction,
13401340
best_block: BestBlock, counterparty_node_id: PublicKey, channel_id: ChannelId,
13411341
) -> ChannelMonitor<Signer> {
1342+
1343+
keys.provide_channel_parameters(channel_parameters);
13421344

13431345
assert!(commitment_transaction_number_obscure_factor <= (1 << 48));
1344-
let counterparty_payment_script = chan_utils::get_counterparty_payment_script(
1345-
&channel_parameters.channel_type_features, &keys.pubkeys().payment_point
1346-
);
1346+
let counterparty_payment_script = keys.get_counterparty_payment_script(true);
13471347

13481348
let counterparty_channel_parameters = channel_parameters.counterparty_parameters.as_ref().unwrap();
13491349
let counterparty_delayed_payment_base_key = counterparty_channel_parameters.pubkeys.delayed_payment_basepoint;
@@ -3398,9 +3398,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
33983398
&broadcaster_keys, &countersignatory_keys, &self.onchain_tx_handler.secp_ctx);
33993399
let channel_parameters =
34003400
&self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable();
3401+
let counterparty_txout = TxOut {
3402+
script_pubkey: self.counterparty_payment_script.clone(),
3403+
value: Amount::from_sat(to_countersignatory_value),
3404+
};
34013405

34023406
CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
3403-
to_broadcaster_value, to_countersignatory_value, broadcaster_funding_key,
3407+
to_broadcaster_value, counterparty_txout, broadcaster_funding_key,
34043408
countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs,
34053409
channel_parameters)
34063410
}

lightning/src/ln/chan_utils.rs

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use bitcoin::sighash::EcdsaSighashType;
2020
use bitcoin::transaction::Version;
2121

2222
use bitcoin::hashes::{Hash, HashEngine};
23-
use bitcoin::hashes::hash160::Hash as Hash160;
2423
use bitcoin::hashes::sha256::Hash as Sha256;
2524
use bitcoin::hashes::ripemd160::Hash as Ripemd160;
2625
use bitcoin::hash_types::Txid;
@@ -1135,7 +1134,13 @@ impl HolderCommitmentTransaction {
11351134
for _ in 0..htlcs.len() {
11361135
counterparty_htlc_sigs.push(dummy_sig);
11371136
}
1138-
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
1137+
let channel_parameters = channel_parameters.as_counterparty_broadcastable();
1138+
let counterparty_payment_script = get_counterparty_payment_script(&channel_parameters.channel_type_features(), &channel_parameters.countersignatory_pubkeys().payment_point);
1139+
let counterparty_txout = TxOut {
1140+
script_pubkey: counterparty_payment_script,
1141+
value: Amount::ZERO,
1142+
};
1143+
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, counterparty_txout, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters);
11391144
htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
11401145
HolderCommitmentTransaction {
11411146
inner,
@@ -1445,12 +1450,12 @@ impl CommitmentTransaction {
14451450
/// Only include HTLCs that are above the dust limit for the channel.
14461451
///
14471452
/// This is not exported to bindings users due to the generic though we likely should expose a version without
1448-
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
1453+
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_txout: TxOut, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
14491454
let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat);
1450-
let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat);
1455+
let to_countersignatory_value_sat = to_countersignatory_txout.value;
14511456

14521457
// Sort outputs and populate output indices while keeping track of the auxiliary data
1453-
let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
1458+
let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_txout, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
14541459

14551460
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
14561461
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
@@ -1479,11 +1484,15 @@ impl CommitmentTransaction {
14791484
self
14801485
}
14811486

1482-
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<BuiltCommitmentTransaction, ()> {
1487+
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, to_countersignatory_spk: ScriptBuf) -> Result<BuiltCommitmentTransaction, ()> {
14831488
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);
14841489

1490+
let to_countersignatory_txout = TxOut {
1491+
script_pubkey: to_countersignatory_spk,
1492+
value: self.to_countersignatory_value_sat,
1493+
};
14851494
let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
1486-
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;
1495+
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, to_countersignatory_txout, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;
14871496

14881497
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
14891498
let txid = transaction.compute_txid();
@@ -1507,23 +1516,14 @@ impl CommitmentTransaction {
15071516
// - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
15081517
// caller needs to have sorted together with the HTLCs so it can keep track of the output index
15091518
// - building of a bitcoin transaction during a verify() call, in which case T is just ()
1510-
fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
1511-
let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys();
1519+
fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_txout: TxOut, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
15121520
let contest_delay = channel_parameters.contest_delay();
15131521

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

1516-
if to_countersignatory_value_sat > Amount::ZERO {
1517-
let script = if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
1518-
get_to_countersignatory_with_anchors_redeemscript(&countersignatory_pubkeys.payment_point).to_p2wsh()
1519-
} else {
1520-
ScriptBuf::new_p2wpkh(&Hash160::hash(&countersignatory_pubkeys.payment_point.serialize()).into())
1521-
};
1524+
if to_countersignatory_txout.value > Amount::ZERO {
15221525
txouts.push((
1523-
TxOut {
1524-
script_pubkey: script.clone(),
1525-
value: to_countersignatory_value_sat,
1526-
},
1526+
to_countersignatory_txout.clone(),
15271527
None,
15281528
))
15291529
}
@@ -1555,7 +1555,7 @@ impl CommitmentTransaction {
15551555
));
15561556
}
15571557

1558-
if to_countersignatory_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() {
1558+
if to_countersignatory_txout.value > Amount::ZERO || !htlcs_with_aux.is_empty() {
15591559
let anchor_script = get_anchor_redeemscript(countersignatory_funding_key);
15601560
txouts.push((
15611561
TxOut {
@@ -1680,14 +1680,14 @@ impl CommitmentTransaction {
16801680
///
16811681
/// An external validating signer must call this method before signing
16821682
/// or using the built transaction.
1683-
pub fn verify<T: secp256k1::Signing + secp256k1::Verification>(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>) -> Result<TrustedCommitmentTransaction, ()> {
1683+
pub fn verify<T: secp256k1::Signing + secp256k1::Verification>(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>, to_countersignatory_spk: ScriptBuf) -> Result<TrustedCommitmentTransaction, ()> {
16841684
// This is the only field of the key cache that we trust
16851685
let per_commitment_point = self.keys.per_commitment_point;
16861686
let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, broadcaster_keys, countersignatory_keys, secp_ctx);
16871687
if keys != self.keys {
16881688
return Err(());
16891689
}
1690-
let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey)?;
1690+
let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, to_countersignatory_spk)?;
16911691
if self.built.transaction != tx.transaction || self.built.txid != tx.txid {
16921692
return Err(());
16931693
}
@@ -1894,11 +1894,11 @@ pub fn get_commitment_transaction_number_obscure_factor(
18941894
mod tests {
18951895
use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
18961896
use crate::chain;
1897-
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
1897+
use crate::ln::chan_utils::{get_counterparty_payment_script, get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
18981898
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
18991899
use crate::util::test_utils;
19001900
use crate::sign::{ChannelSigner, SignerProvider};
1901-
use bitcoin::{Network, Txid, ScriptBuf, CompressedPublicKey};
1901+
use bitcoin::{Amount, TxOut, Network, Txid, ScriptBuf, CompressedPublicKey};
19021902
use bitcoin::hashes::Hash;
19031903
use bitcoin::hex::FromHex;
19041904
use crate::types::payment::PaymentHash;
@@ -1957,12 +1957,20 @@ mod tests {
19571957
}
19581958

19591959
fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction {
1960+
let channel_parameters = self.channel_parameters.as_holder_broadcastable();
1961+
let counterparty_payment_script = get_counterparty_payment_script(&channel_parameters.channel_type_features(), &channel_parameters.countersignatory_pubkeys().payment_point);
1962+
let counterparty_txout = TxOut {
1963+
script_pubkey: counterparty_payment_script,
1964+
value: Amount::from_sat(to_countersignatory_sats),
1965+
};
19601966
CommitmentTransaction::new_with_auxiliary_htlc_data(
1961-
self.commitment_number, to_broadcaster_sats, to_countersignatory_sats,
1967+
self.commitment_number,
1968+
to_broadcaster_sats,
1969+
counterparty_txout,
19621970
self.holder_funding_pubkey.clone(),
19631971
self.counterparty_funding_pubkey.clone(),
19641972
self.keys.clone(), self.feerate_per_kw,
1965-
&mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable()
1973+
&mut self.htlcs_with_aux, &channel_parameters,
19661974
)
19671975
}
19681976
}

lightning/src/ln/channel.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use bitcoin::sighash;
1515
use bitcoin::sighash::EcdsaSighashType;
1616
use bitcoin::consensus::encode;
1717
use bitcoin::absolute::LockTime;
18-
use bitcoin::Weight;
18+
use bitcoin::{TxOut, Weight};
1919

2020
use bitcoin::hashes::Hash;
2121
use bitcoin::hashes::sha256::Hash as Sha256;
@@ -1609,8 +1609,7 @@ trait InitialRemoteCommitmentReceiver<SP: Deref> where SP::Target: SignerProvide
16091609
let funding_txo_script = funding_redeemscript.to_p2wsh();
16101610
let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound());
16111611
let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner());
1612-
let mut monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id);
1613-
monitor_signer.provide_channel_parameters(&context.channel_transaction_parameters);
1612+
let monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id);
16141613
let channel_monitor = ChannelMonitor::new(context.secp_ctx.clone(), monitor_signer,
16151614
shutdown_script, context.get_holder_selected_contest_delay(),
16161615
&context.destination_script, (funding_txo, funding_txo_script),
@@ -3165,9 +3164,14 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
31653164
let channel_parameters =
31663165
if local { self.channel_transaction_parameters.as_holder_broadcastable() }
31673166
else { self.channel_transaction_parameters.as_counterparty_broadcastable() };
3167+
let counterparty_payment_script = self.holder_signer.as_ref().get_counterparty_payment_script(!local);
3168+
let counterparty_txout = TxOut {
3169+
script_pubkey: counterparty_payment_script,
3170+
value: Amount::from_sat(value_to_b as u64),
3171+
};
31683172
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
31693173
value_to_a as u64,
3170-
value_to_b as u64,
3174+
counterparty_txout,
31713175
funding_pubkey_a,
31723176
funding_pubkey_b,
31733177
keys.clone(),
@@ -10839,6 +10843,7 @@ mod tests {
1083910843
use crate::ln::channel::{HTLCOutputInCommitment ,TxCreationKeys};
1084010844
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint};
1084110845
use crate::ln::chan_utils::{ChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
10846+
use crate::sign::type_resolver::ChannelSignerType;
1084210847
use crate::util::logger::Logger;
1084310848
use crate::sync::Arc;
1084410849
use core::str::FromStr;
@@ -10912,13 +10917,19 @@ mod tests {
1091210917
macro_rules! test_commitment {
1091310918
( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => {
1091410919
chan.context.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
10920+
let mut holder_signer = keys_provider.derive_channel_signer(chan.context.channel_value_satoshis, chan.context.channel_keys_id);
10921+
holder_signer.provide_channel_parameters(&chan.context.channel_transaction_parameters);
10922+
chan.context.holder_signer = ChannelSignerType::Ecdsa(holder_signer);
1091510923
test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::only_static_remote_key(), $($remain)*);
1091610924
};
1091710925
}
1091810926

1091910927
macro_rules! test_commitment_with_anchors {
1092010928
( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => {
1092110929
chan.context.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
10930+
let mut holder_signer = keys_provider.derive_channel_signer(chan.context.channel_value_satoshis, chan.context.channel_keys_id);
10931+
holder_signer.provide_channel_parameters(&chan.context.channel_transaction_parameters);
10932+
chan.context.holder_signer = ChannelSignerType::Ecdsa(holder_signer);
1092210933
test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), $($remain)*);
1092310934
};
1092410935
}

lightning/src/ln/functional_tests.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -766,16 +766,22 @@ fn test_update_fee_that_funder_cannot_afford() {
766766
|phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
767767
).flatten().unwrap();
768768
let local_chan_signer = local_chan.get_signer();
769+
let channel_parameters = local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable();
770+
let counterparty_payment_script = local_chan_signer.as_ref().get_counterparty_payment_script(true);
771+
let counterparty_txout = TxOut {
772+
script_pubkey: counterparty_payment_script,
773+
value: Amount::from_sat(channel_value - push_sats - commit_tx_fee_msat(non_buffer_feerate + 4, 0, &channel_type_features) / 1000),
774+
};
769775
let mut htlcs: Vec<(HTLCOutputInCommitment, ())> = vec![];
770776
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
771777
INITIAL_COMMITMENT_NUMBER - 1,
772778
push_sats,
773-
channel_value - push_sats - commit_tx_fee_msat(non_buffer_feerate + 4, 0, &channel_type_features) / 1000,
779+
counterparty_txout,
774780
local_funding, remote_funding,
775781
commit_tx_keys.clone(),
776782
non_buffer_feerate + 4,
777783
&mut htlcs,
778-
&local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable()
784+
&channel_parameters,
779785
);
780786
local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), Vec::new(), &secp_ctx).unwrap()
781787
};
@@ -1517,15 +1523,21 @@ fn test_fee_spike_violation_fails_htlc() {
15171523
|phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
15181524
).flatten().unwrap();
15191525
let local_chan_signer = local_chan.get_signer();
1526+
let channel_parameters = local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable();
1527+
let counterparty_payment_script = local_chan_signer.as_ref().get_counterparty_payment_script(true);
1528+
let counterparty_txout = TxOut {
1529+
script_pubkey: counterparty_payment_script,
1530+
value: Amount::from_sat(local_chan_balance),
1531+
};
15201532
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
15211533
commitment_number,
15221534
95000,
1523-
local_chan_balance,
1535+
counterparty_txout,
15241536
local_funding, remote_funding,
15251537
commit_tx_keys.clone(),
15261538
feerate_per_kw,
15271539
&mut vec![(accepted_htlc_info, ())],
1528-
&local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable()
1540+
&channel_parameters,
15291541
);
15301542
local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), Vec::new(), &secp_ctx).unwrap()
15311543
};

0 commit comments

Comments
 (0)