Move justice transaction signature behind ChanSigner
authorAntoine Riard <ariard@student.42.fr>
Tue, 24 Mar 2020 18:22:16 +0000 (14:22 -0400)
committerAntoine Riard <ariard@student.42.fr>
Mon, 18 May 2020 08:46:48 +0000 (04:46 -0400)
lightning/src/chain/keysinterface.rs
lightning/src/ln/chan_utils.rs
lightning/src/ln/channelmonitor.rs
lightning/src/ln/onchaintx.rs
lightning/src/util/enforcing_trait_impls.rs

index cf1f4f869f5c395169d09faeb26e418d0e812db7..2dc42abad3ae4cb92bd6284de47c816b4a801d1f 100644 (file)
@@ -246,6 +246,27 @@ pub trait ChannelKeys : Send+Clone {
        /// return value must contain a signature.
        fn sign_local_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &LocalCommitmentTransaction, local_csv: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()>;
 
+       /// Create a signature for a transaction spending an HTLC or commitment transaction output
+       /// when our counterparty broadcast an old state.
+       ///
+       /// Justice transaction may claim multiples outputs at same time if timelock are similar.
+       /// It may be called multiples time for same output(s) if a fee-bump is needed with regards
+       /// to an upcoming timelock expiration.
+       ///
+       /// Witness_script is a revokable witness script as defined in BOLT3 for `to_local`/HTLC
+       /// outputs.
+       ///
+       /// Input index is a pointer towards outpoint spent, commited by sigs (BIP 143).
+       ///
+       /// Amount is value of the output spent by this input, committed by sigs (BIP 143).
+       ///
+       /// Per_commitment key is revocation secret such as provided by remote party while
+       /// revocating detected onchain transaction. It's not a _local_ secret key, therefore
+       /// it may cross interfaces, a node compromise won't allow to spend revoked output without
+       /// also compromissing revocation key.
+       //TODO: dry-up witness_script and pass pubkeys
+       fn sign_justice_transaction<T: secp256k1::Signing>(&self, justice_tx: &Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
+
        /// Create a signature for a (proposed) closing transaction.
        ///
        /// Note that, due to rounding, there may be one "missing" satoshi, and either party may have
@@ -394,6 +415,15 @@ impl ChannelKeys for InMemoryChannelKeys {
                local_commitment_tx.get_htlc_sigs(&self.htlc_base_key, local_csv, secp_ctx)
        }
 
+       fn sign_justice_transaction<T: secp256k1::Signing>(&self, justice_tx: &Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
+               if let Ok(revocation_key) = chan_utils::derive_private_revocation_key(&secp_ctx, &per_commitment_key, &self.revocation_base_key) {
+                       let sighash_parts = bip143::SighashComponents::new(&justice_tx);
+                       let sighash = hash_to_message!(&sighash_parts.sighash_all(&justice_tx.input[input], &witness_script, amount)[..]);
+                       return Ok(secp_ctx.sign(&sighash, &revocation_key))
+               }
+               Err(())
+       }
+
        fn sign_closing_transaction<T: secp256k1::Signing>(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
                if closing_tx.input.len() != 1 { return Err(()); }
                if closing_tx.input[0].witness.len() != 0 { return Err(()); }
index c229819c3b64e6d3e875002c814190a1d40e153e..f79498c58dd5087baffe68c91e754b6102587587 100644 (file)
@@ -173,7 +173,7 @@ impl Readable for CounterpartyCommitmentSecrets {
 
 /// Derives a per-commitment-transaction private key (eg an htlc key or payment key) from the base
 /// private key for that type of key and the per_commitment_point (available in TxCreationKeys)
-pub fn derive_private_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
+pub(crate) fn derive_private_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
        let mut sha = Sha256::engine();
        sha.input(&per_commitment_point.serialize());
        sha.input(&PublicKey::from_secret_key(&secp_ctx, &base_secret).serialize());
@@ -197,7 +197,7 @@ pub(super) fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>,
 /// Derives a revocation key from its constituent parts.
 /// Note that this is infallible iff we trust that at least one of the two input keys are randomly
 /// generated (ie our own).
-pub(super) fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
+pub fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
        let revocation_base_point = PublicKey::from_secret_key(&secp_ctx, &revocation_base_secret);
        let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
 
index 731e7775f085afdb074d732ade90660ebbde4b16..c285d364f3dc1673979dd8bc8b3e21a8439d7323 100644 (file)
@@ -435,7 +435,7 @@ struct LocalSignedTx {
 pub(crate) enum InputMaterial {
        Revoked {
                per_commitment_point: PublicKey,
-               key: SecretKey,
+               per_commitment_key: SecretKey,
                input_descriptor: InputDescriptors,
                amount: u64,
        },
@@ -458,10 +458,10 @@ pub(crate) enum InputMaterial {
 impl Writeable for InputMaterial  {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
                match self {
-                       &InputMaterial::Revoked { ref per_commitment_point, ref key, ref input_descriptor, ref amount} => {
+                       &InputMaterial::Revoked { ref per_commitment_point, ref per_commitment_key, ref input_descriptor, ref amount} => {
                                writer.write_all(&[0; 1])?;
                                per_commitment_point.write(writer)?;
-                               writer.write_all(&key[..])?;
+                               writer.write_all(&per_commitment_key[..])?;
                                input_descriptor.write(writer)?;
                                writer.write_all(&byte_utils::be64_to_array(*amount))?;
                        },
@@ -492,12 +492,12 @@ impl Readable for InputMaterial {
                let input_material = match <u8 as Readable>::read(reader)? {
                        0 => {
                                let per_commitment_point = Readable::read(reader)?;
-                               let key = Readable::read(reader)?;
+                               let per_commitment_key = Readable::read(reader)?;
                                let input_descriptor = Readable::read(reader)?;
                                let amount = Readable::read(reader)?;
                                InputMaterial::Revoked {
                                        per_commitment_point,
-                                       key,
+                                       per_commitment_key,
                                        input_descriptor,
                                        amount
                                }
@@ -1426,7 +1426,6 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
                        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 = ignore_error!(chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &self.keys.pubkeys().revocation_basepoint));
-                       let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &self.keys.revocation_base_key()));
                        let delayed_key = ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key), &self.their_delayed_payment_base_key));
 
                        let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key);
@@ -1435,7 +1434,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
                        // First, process non-htlc outputs (to_local & to_remote)
                        for (idx, outp) in tx.output.iter().enumerate() {
                                if outp.script_pubkey == revokeable_p2wsh {
-                                       let witness_data = InputMaterial::Revoked { per_commitment_point, key: revocation_key, input_descriptor: InputDescriptors::RevokedOutput, amount: outp.value };
+                                       let witness_data = InputMaterial::Revoked { per_commitment_point, per_commitment_key, input_descriptor: InputDescriptors::RevokedOutput, amount: outp.value };
                                        claimable_outpoints.push(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, witness_data});
                                }
                        }
@@ -1448,7 +1447,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
                                                                tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 {
                                                        return (claimable_outpoints, (commitment_txid, watch_outputs)); // Corrupted per_commitment_data, fuck this user
                                                }
-                                               let witness_data = InputMaterial::Revoked { per_commitment_point, key: revocation_key, input_descriptor: if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }, amount: tx.output[transaction_output_index as usize].value };
+                                               let witness_data = InputMaterial::Revoked { per_commitment_point, per_commitment_key, input_descriptor: if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }, amount: tx.output[transaction_output_index as usize].value };
                                                claimable_outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
                                        }
                                }
@@ -1613,10 +1612,9 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
                let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (Vec::new(), 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_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &self.keys.revocation_base_key()));
 
                log_trace!(logger, "Remote HTLC broadcast {}:{}", htlc_txid, 0);
-               let witness_data = InputMaterial::Revoked { per_commitment_point, key: revocation_key, input_descriptor: InputDescriptors::RevokedOutput, amount: tx.output[0].value };
+               let witness_data = InputMaterial::Revoked { per_commitment_point, per_commitment_key, input_descriptor: InputDescriptors::RevokedOutput, amount: tx.output[0].value };
                let claimable_outpoints = vec!(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data });
                (claimable_outpoints, Some((htlc_txid, tx.output.clone())))
        }
index 7ee0ccb100fb145b33a7d30cf1561966adb77ea2..6de7f02194578d9188b88c0643a262e9f450b99b 100644 (file)
@@ -632,7 +632,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
 
                        for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() {
                                match per_outp_material {
-                                       &InputMaterial::Revoked { ref per_commitment_point, ref key, ref input_descriptor, ref amount } => {
+                                       &InputMaterial::Revoked { ref per_commitment_point, ref per_commitment_key, ref input_descriptor, ref amount } => {
                                                if let Ok(chan_keys) = TxCreationKeys::new(&self.secp_ctx, &per_commitment_point, &self.remote_tx_cache.remote_delayed_payment_base_key, &self.remote_tx_cache.remote_htlc_base_key, &self.key_storage.pubkeys().revocation_basepoint, &self.key_storage.pubkeys().htlc_basepoint) {
 
                                                        let mut this_htlc = None;
@@ -654,17 +654,19 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
                                                                chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, self.remote_csv, &chan_keys.a_delayed_payment_key)
                                                        };
 
-                                                       let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
-                                                       let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &witness_script, *amount)[..]);
-                                                       let sig = self.secp_ctx.sign(&sighash, &key);
-                                                       bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
-                                                       bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
-                                                       if *input_descriptor != InputDescriptors::RevokedOutput {
-                                                               bumped_tx.input[i].witness.push(chan_keys.revocation_key.clone().serialize().to_vec());
-                                                       } else {
-                                                               bumped_tx.input[i].witness.push(vec!(1));
-                                                       }
-                                                       bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
+                                                       let is_htlc = *input_descriptor != InputDescriptors::RevokedOutput;
+                                                       if let Ok(sig) = self.key_storage.sign_justice_transaction(&bumped_tx, i, &witness_script, *amount, &per_commitment_key, &chan_keys.revocation_key, is_htlc,  &self.secp_ctx) {
+                                                               bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
+                                                               bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
+                                                               if is_htlc {
+                                                                       bumped_tx.input[i].witness.push(chan_keys.revocation_key.clone().serialize().to_vec());
+                                                               } else {
+                                                                       bumped_tx.input[i].witness.push(vec!(1));
+                                                               }
+                                                               bumped_tx.input[i].witness.push(witness_script.clone().into_bytes());
+                                                       } else { return None; }
+                                                       //TODO: panic ?
+
                                                        log_trace!(logger, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if *input_descriptor == InputDescriptors::RevokedOutput { "to_local" } else if *input_descriptor == InputDescriptors::RevokedOfferedHTLC { "offered" } else if *input_descriptor == InputDescriptors::RevokedReceivedHTLC { "received" } else { "" }, outp.vout, outp.txid, new_feerate);
                                                }
                                        },
index d8db20f0f9a6008d9b23e8f85072a85ed48609fa..c9c503d9ae5646ff54c50fa07838f0bc99ee9791 100644 (file)
@@ -6,6 +6,7 @@ use std::cmp;
 use std::sync::{Mutex, Arc};
 
 use bitcoin::blockdata::transaction::Transaction;
+use bitcoin::blockdata::script::Script;
 use bitcoin::util::bip143;
 
 use bitcoin::secp256k1;
@@ -103,6 +104,10 @@ impl ChannelKeys for EnforcingChannelKeys {
                Ok(self.inner.sign_local_commitment_htlc_transactions(local_commitment_tx, local_csv, secp_ctx).unwrap())
        }
 
+       fn sign_justice_transaction<T: secp256k1::Signing>(&self, justice_tx: &Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
+               Ok(self.inner.sign_justice_transaction(justice_tx, input, witness_script, amount, per_commitment_key, revocation_pubkey, is_htlc, secp_ctx).unwrap())
+       }
+
        fn sign_closing_transaction<T: secp256k1::Signing>(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
                Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap())
        }