- if let Some(revocation_point) = revocation_point_option {
- let (revocation_pubkey, b_htlc_key) = match self.key_storage {
- Storage::Local { ref revocation_base_key, ref htlc_base_key, .. } => {
- (ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, revocation_point, &PublicKey::from_secret_key(&self.secp_ctx, &revocation_base_key))),
- ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, revocation_point, &PublicKey::from_secret_key(&self.secp_ctx, &htlc_base_key))))
- },
- Storage::Watchtower { ref revocation_base_key, ref htlc_base_key, .. } => {
- (ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, revocation_point, &revocation_base_key)),
- ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, revocation_point, &htlc_base_key)))
- },
- };
- let a_htlc_key = match self.their_htlc_base_key {
- None => return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs),
- Some(their_htlc_base_key) => ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, revocation_point, &their_htlc_base_key)),
- };
-
- for (idx, outp) in tx.output.iter().enumerate() {
- if outp.script_pubkey.is_v0_p2wpkh() {
- match self.key_storage {
- Storage::Local { ref payment_base_key, .. } => {
- if let Ok(local_key) = chan_utils::derive_private_key(&self.secp_ctx, &revocation_point, &payment_base_key) {
- spendable_outputs.push(SpendableOutputDescriptor::DynamicOutputP2WPKH {
- outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 },
- key: local_key,
- output: outp.clone(),
- });
- }
- },
- Storage::Watchtower { .. } => {}
- }
- break; // Only to_remote ouput is claimable
- }
- }
-
- let mut total_value = 0;
- let mut inputs = Vec::new();
- let mut inputs_desc = Vec::new();
- let mut inputs_info = Vec::new();
-
- macro_rules! sign_input {
- ($sighash_parts: expr, $input: expr, $amount: expr, $preimage: expr) => {
- {
- let (sig, redeemscript, htlc_key) = match self.key_storage {
- Storage::Local { ref htlc_base_key, .. } => {
- let htlc = &per_commitment_option.unwrap()[$input.sequence as usize].0;
- let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
- let sighash = hash_to_message!(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]);
- let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key));
- (self.secp_ctx.sign(&sighash, &htlc_key), redeemscript, htlc_key)
- },
- Storage::Watchtower { .. } => {
- unimplemented!();
- }
- };
- $input.witness.push(sig.serialize_der().to_vec());
- $input.witness[0].push(SigHashType::All as u8);
- $input.witness.push($preimage);
- $input.witness.push(redeemscript.clone().into_bytes());
- (redeemscript, htlc_key)
- }
- }
- }
-
- for (idx, &(ref htlc, _)) in per_commitment_data.iter().enumerate() {
- if let Some(transaction_output_index) = htlc.transaction_output_index {
- let expected_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey);
- if transaction_output_index as usize >= tx.output.len() ||
- tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 ||
- tx.output[transaction_output_index as usize].script_pubkey != expected_script.to_v0_p2wsh() {
- return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); // Corrupted per_commitment_data, fuck this user
- }
- if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
- let input = TxIn {
- previous_output: BitcoinOutPoint {
- txid: commitment_txid,
- vout: transaction_output_index,
- },
- script_sig: Script::new(),
- sequence: idx as u32, // reset to 0xfffffffd in sign_input
- witness: Vec::new(),
- };
- if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
- inputs.push(input);
- inputs_desc.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC });
- inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
- total_value += tx.output[transaction_output_index as usize].value;
- } else {
- let mut single_htlc_tx = Transaction {
- version: 2,
- lock_time: 0,
- input: vec![input],
- output: vec!(TxOut {
- script_pubkey: self.destination_script.clone(),
- value: htlc.amount_msat / 1000,
- }),
- };
- let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]);
- let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
- let mut used_feerate;
- if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
- let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
- let (redeemscript, htlc_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], htlc.amount_msat / 1000, payment_preimage.0.to_vec());
- assert!(predicted_weight >= single_htlc_tx.get_weight());
- spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
- outpoint: BitcoinOutPoint { txid: single_htlc_tx.txid(), vout: 0 },
- output: single_htlc_tx.output[0].clone(),
- });
- match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
- hash_map::Entry::Occupied(_) => {},
- hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
- }
- txn_to_broadcast.push(single_htlc_tx);
- }
- }
- }
- if !htlc.offered {
- // TODO: If the HTLC has already expired, potentially merge it with the
- // rest of the claim transaction, as above.
- let input = TxIn {
- previous_output: BitcoinOutPoint {
- txid: commitment_txid,
- vout: transaction_output_index,
- },
- script_sig: Script::new(),
- sequence: idx as u32,
- witness: Vec::new(),
- };
- let mut timeout_tx = Transaction {
- version: 2,
- lock_time: htlc.cltv_expiry,
- input: vec![input],
- output: vec!(TxOut {
- script_pubkey: self.destination_script.clone(),
- value: htlc.amount_msat / 1000,
- }),
- };
- let predicted_weight = timeout_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::ReceivedHTLC]);
- let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
- let mut used_feerate;
- if subtract_high_prio_fee!(self, fee_estimator, timeout_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
- let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
- let (redeemscript, htlc_key) = sign_input!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000, vec![0]);
- assert!(predicted_weight >= timeout_tx.get_weight());
- //TODO: track SpendableOutputDescriptor
- match self.our_claim_txn_waiting_first_conf.entry(timeout_tx.input[0].previous_output.clone()) {
- hash_map::Entry::Occupied(_) => {},
- hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script : redeemscript, key: htlc_key, preimage: None, amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
- }
- }
- txn_to_broadcast.push(timeout_tx);
- }
- }
- }
-
- if inputs.is_empty() { return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); } // Nothing to be done...probably a false positive/local tx
-
- let outputs = vec!(TxOut {
- script_pubkey: self.destination_script.clone(),
- value: total_value
- });
- let mut spend_tx = Transaction {
- version: 2,
- lock_time: 0,
- input: inputs,
- output: outputs,
- };
-
- let mut predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&inputs_desc[..]);
-
- let mut used_feerate;
- if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
- return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs);
- }
-
- let sighash_parts = bip143::SighashComponents::new(&spend_tx);
-
- for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) {
- let (redeemscript, htlc_key) = sign_input!(sighash_parts, input, info.1, (info.0).0.to_vec());
- let height_timer = Self::get_height_timer(height, info.2);
- match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) {
- hash_map::Entry::Occupied(_) => {},
- hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*(info.0)), amount: info.1}, used_feerate, info.2, height)); }
- }
- }
- assert!(predicted_weight >= spend_tx.get_weight());
- spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
- outpoint: BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 },
- output: spend_tx.output[0].clone(),
- });
- txn_to_broadcast.push(spend_tx);
- }
- }
- } else if let Some((ref to_remote_rescue, ref local_key)) = self.to_remote_rescue {
- for (idx, outp) in tx.output.iter().enumerate() {
- if to_remote_rescue == &outp.script_pubkey {
- spendable_outputs.push(SpendableOutputDescriptor::DynamicOutputP2WPKH {
- outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 },
- key: local_key.clone(),
- output: outp.clone(),
- });
- }
- }
- }
-
- (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs)
- }
-
- /// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
- fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32, fee_estimator: &FeeEstimator) -> (Option<Transaction>, Option<SpendableOutputDescriptor>) {
- if tx.input.len() != 1 || tx.output.len() != 1 {
- return (None, None)
- }
-
- macro_rules! ignore_error {
- ( $thing : expr ) => {
- match $thing {
- Ok(a) => a,
- Err(_) => return (None, None)
- }
- };
- }
-
- let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (None, None); };
- let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret));
- let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
- let revocation_pubkey = match self.key_storage {
- Storage::Local { ref revocation_base_key, .. } => {
- ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &PublicKey::from_secret_key(&self.secp_ctx, &revocation_base_key)))
- },
- Storage::Watchtower { ref revocation_base_key, .. } => {
- ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &revocation_base_key))
- },
- };
- let delayed_key = match self.their_delayed_payment_base_key {
- None => return (None, None),
- Some(their_delayed_payment_base_key) => ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &per_commitment_point, &their_delayed_payment_base_key)),
- };
- let redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key);
- let revokeable_p2wsh = redeemscript.to_v0_p2wsh();
- let htlc_txid = tx.txid(); //TODO: This is gonna be a performance bottleneck for watchtowers!
-
- let mut inputs = Vec::new();
- let mut amount = 0;
-
- if tx.output[0].script_pubkey == revokeable_p2wsh { //HTLC transactions have one txin, one txout
- inputs.push(TxIn {
- previous_output: BitcoinOutPoint {
- txid: htlc_txid,
- vout: 0,
- },
- script_sig: Script::new(),
- sequence: 0xfffffffd,
- witness: Vec::new(),
- });
- amount = tx.output[0].value;
- }
-
- if !inputs.is_empty() {
- let outputs = vec!(TxOut {
- script_pubkey: self.destination_script.clone(),
- value: amount
- });
-
- let mut spend_tx = Transaction {
- version: 2,
- lock_time: 0,
- input: inputs,
- output: outputs,
- };
- let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::RevokedOutput]);
- let mut used_feerate;
- if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
- return (None, None);
- }
-
- let sighash_parts = bip143::SighashComponents::new(&spend_tx);
-
- let (sig, revocation_key) = match self.key_storage {
- Storage::Local { ref revocation_base_key, .. } => {
- let sighash = hash_to_message!(&sighash_parts.sighash_all(&spend_tx.input[0], &redeemscript, amount)[..]);
- let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &revocation_base_key));
- (self.secp_ctx.sign(&sighash, &revocation_key), revocation_key)
- }
- Storage::Watchtower { .. } => {
- unimplemented!();
- }
- };
- spend_tx.input[0].witness.push(sig.serialize_der().to_vec());
- spend_tx.input[0].witness[0].push(SigHashType::All as u8);
- spend_tx.input[0].witness.push(vec!(1));
- spend_tx.input[0].witness.push(redeemscript.clone().into_bytes());
-
- assert!(predicted_weight >= spend_tx.get_weight());
- let outpoint = BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 };
- let output = spend_tx.output[0].clone();
- let height_timer = Self::get_height_timer(height, self.their_to_self_delay.unwrap() as u32); // We can safely unwrap given we are past channel opening
- match self.our_claim_txn_waiting_first_conf.entry(spend_tx.input[0].previous_output.clone()) {
- hash_map::Entry::Occupied(_) => {},
- hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::Revoked { script: redeemscript, pubkey: None, key: revocation_key, is_htlc: false, amount: tx.output[0].value }, used_feerate, height + self.our_to_self_delay as u32, height)); }
- }
- (Some(spend_tx), Some(SpendableOutputDescriptor::StaticOutput { outpoint, output }))
- } else { (None, None) }
- }