+ use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
+ use crate::{hex, chain};
+ use crate::prelude::*;
+ use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
+ use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
+ use crate::util::test_utils;
+ use crate::sign::{ChannelSigner, SignerProvider};
+ use bitcoin::{Network, Txid, Script};
+ use bitcoin::hashes::Hash;
+ use crate::ln::PaymentHash;
+ use bitcoin::hashes::hex::ToHex;
+ use bitcoin::util::address::Payload;
+ use bitcoin::PublicKey as BitcoinPublicKey;
+ use crate::ln::features::ChannelTypeFeatures;
+
+ struct TestCommitmentTxBuilder {
+ commitment_number: u64,
+ holder_funding_pubkey: PublicKey,
+ counterparty_funding_pubkey: PublicKey,
+ keys: TxCreationKeys,
+ feerate_per_kw: u32,
+ htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>,
+ channel_parameters: ChannelTransactionParameters,
+ counterparty_pubkeys: ChannelPublicKeys,
+ }
+
+ impl TestCommitmentTxBuilder {
+ fn new() -> Self {
+ let secp_ctx = Secp256k1::new();
+ let seed = [42; 32];
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
+ let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0));
+ let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1));
+ let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint;
+ let per_commitment_secret = SecretKey::from_slice(&hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
+ let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
+ let htlc_basepoint = &signer.pubkeys().htlc_basepoint;
+ let holder_pubkeys = signer.pubkeys();
+ let counterparty_pubkeys = counterparty_signer.pubkeys().clone();
+ let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
+ let channel_parameters = ChannelTransactionParameters {
+ holder_pubkeys: holder_pubkeys.clone(),
+ holder_selected_contest_delay: 0,
+ is_outbound_from_holder: false,
+ counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
+ funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
+ channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
+ };
+ let htlcs_with_aux = Vec::new();
+
+ Self {
+ commitment_number: 0,
+ holder_funding_pubkey: holder_pubkeys.funding_pubkey,
+ counterparty_funding_pubkey: counterparty_pubkeys.funding_pubkey,
+ keys,
+ feerate_per_kw: 1,
+ htlcs_with_aux,
+ channel_parameters,
+ counterparty_pubkeys,
+ }
+ }
+
+ fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction {
+ CommitmentTransaction::new_with_auxiliary_htlc_data(
+ self.commitment_number, to_broadcaster_sats, to_countersignatory_sats,
+ self.holder_funding_pubkey.clone(),
+ self.counterparty_funding_pubkey.clone(),
+ self.keys.clone(), self.feerate_per_kw,
+ &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable()
+ )
+ }
+ }
+
+ #[test]
+ fn test_anchors() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Generate broadcaster and counterparty outputs
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, Payload::p2wpkh(&BitcoinPublicKey::new(builder.counterparty_pubkeys.payment_point)).unwrap().script_pubkey());
+
+ // Generate broadcaster and counterparty outputs as well as two anchors
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 4);
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&builder.counterparty_pubkeys.payment_point).to_v0_p2wsh());
+
+ // Generate broadcaster output and anchor
+ let tx = builder.build(3000, 0);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ // Generate counterparty output and anchor
+ let tx = builder.build(0, 3000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ let received_htlc = HTLCOutputInCommitment {
+ offered: false,
+ amount_msat: 400000,
+ cltv_expiry: 100,
+ payment_hash: PaymentHash([42; 32]),
+ transaction_output_index: None,
+ };
+
+ let offered_htlc = HTLCOutputInCommitment {
+ offered: true,
+ amount_msat: 600000,
+ cltv_expiry: 100,
+ payment_hash: PaymentHash([43; 32]),
+ transaction_output_index: None,
+ };
+
+ // Generate broadcaster output and received and offered HTLC outputs, w/o anchors
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
+ let keys = &builder.keys.clone();
+ assert_eq!(tx.built.transaction.output.len(), 3);
+ assert_eq!(tx.built.transaction.output[0].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[1].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh().to_hex(),
+ "0020e43a7c068553003fe68fcae424fb7b28ec5ce48cd8b6744b3945631389bad2fb");
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::only_static_remote_key(), &keys).to_v0_p2wsh().to_hex(),
+ "0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d");
+
+ // Generate broadcaster output and received and offered HTLC outputs, with anchors
+ builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
+ builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())];
+ let tx = builder.build(3000, 0);
+ assert_eq!(tx.built.transaction.output.len(), 5);
+ assert_eq!(tx.built.transaction.output[2].script_pubkey, get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
+ assert_eq!(tx.built.transaction.output[3].script_pubkey, get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh());
+ assert_eq!(get_htlc_redeemscript(&received_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh().to_hex(),
+ "0020b70d0649c72b38756885c7a30908d912a7898dd5d79457a7280b8e9a20f3f2bc");
+ assert_eq!(get_htlc_redeemscript(&offered_htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), &keys).to_v0_p2wsh().to_hex(),
+ "002087a3faeb1950a469c0e2db4a79b093a41b9526e5a6fc6ef5cb949bde3be379c7");
+ }
+
+ #[test]
+ fn test_finding_revokeable_output_index() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), Some(0));
+
+ // Revokeable output present (but to_broadcaster_delay missing)
+ let tx = CommitmentTransaction { to_broadcaster_delay: None, ..tx };
+ assert_eq!(tx.built.transaction.output.len(), 2);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert_eq!(tx.trust().revokeable_output_index(), None);
+ }
+
+ #[test]
+ fn test_building_to_local_justice_tx() {
+ let mut builder = TestCommitmentTxBuilder::new();
+
+ // Revokeable output not present (our balance is dust)
+ let tx = builder.build(0, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 1);
+ assert!(tx.trust().build_to_local_justice_tx(253, Script::new()).is_err());
+
+ // Revokeable output present
+ let tx = builder.build(1000, 2000);
+ assert_eq!(tx.built.transaction.output.len(), 2);
+
+ // Too high feerate
+ assert!(tx.trust().build_to_local_justice_tx(100_000, Script::new()).is_err());
+
+ // Generate a random public key for destination script
+ let secret_key = SecretKey::from_slice(
+ &hex::decode("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
+ .unwrap()[..]).unwrap();
+ let pubkey_hash = BitcoinPublicKey::new(
+ PublicKey::from_secret_key(&Secp256k1::new(), &secret_key)).wpubkey_hash().unwrap();
+ let destination_script = Script::new_v0_p2wpkh(&pubkey_hash);
+
+ let justice_tx = tx.trust().build_to_local_justice_tx(253, destination_script.clone()).unwrap();
+ assert_eq!(justice_tx.input.len(), 1);
+ assert_eq!(justice_tx.input[0].previous_output.txid, tx.built.transaction.txid());
+ assert_eq!(justice_tx.input[0].previous_output.vout, tx.trust().revokeable_output_index().unwrap() as u32);
+ assert!(justice_tx.input[0].sequence.is_rbf());
+
+ assert_eq!(justice_tx.output.len(), 1);
+ assert!(justice_tx.output[0].value < 1000);
+ assert_eq!(justice_tx.output[0].script_pubkey, destination_script);
+ }