Skip to content

Move spendable output descriptors key behind signer interface #562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
91 changes: 81 additions & 10 deletions lightning/src/chain/keysinterface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
//! spendable on-chain outputs which the user owns and is responsible for using just as any other
//! on-chain output which is theirs.

use bitcoin::blockdata::transaction::{Transaction, OutPoint, TxOut};
use bitcoin::blockdata::transaction::{Transaction, OutPoint, TxOut, SigHashType};
use bitcoin::blockdata::script::{Script, Builder};
use bitcoin::blockdata::opcodes;
use bitcoin::network::constants::Network;
use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey, ChildNumber};
use bitcoin::util::address::Address;
use bitcoin::util::bip143;

use bitcoin_hashes::{Hash, HashEngine};
Expand All @@ -26,6 +27,7 @@ use util::ser::{Writeable, Writer, Readable};
use ln::chan_utils;
use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, LocalCommitmentTransaction};
use ln::msgs;
use ln::channelmanager::PaymentPreimage;

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
Expand Down Expand Up @@ -64,8 +66,8 @@ pub enum SpendableOutputDescriptor {
DynamicOutputP2WSH {
/// The outpoint which is spendable
outpoint: OutPoint,
/// The secret key which must be used to sign the spending transaction
key: SecretKey,
/// Per commitment point to derive delayed_payment_key by key holder
per_commitment_point: PublicKey,
/// The witness redeemScript which is hashed to create the script_pubkey in the given output
witness_script: Script,
/// The nSequence value which must be set in the spending input to satisfy the OP_CSV in
Expand All @@ -83,7 +85,7 @@ pub enum SpendableOutputDescriptor {
/// The outpoint which is spendable
outpoint: OutPoint,
/// The secret key which must be used to sign the spending transaction
key: SecretKey,
per_commitment_point: PublicKey,
/// The output which is reference by the given outpoint
output: TxOut,
}
Expand All @@ -97,18 +99,18 @@ impl Writeable for SpendableOutputDescriptor {
outpoint.write(writer)?;
output.write(writer)?;
},
&SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, ref key, ref witness_script, ref to_self_delay, ref output } => {
&SpendableOutputDescriptor::DynamicOutputP2WSH { ref outpoint, ref per_commitment_point, ref witness_script, ref to_self_delay, ref output } => {
1u8.write(writer)?;
outpoint.write(writer)?;
key.write(writer)?;
per_commitment_point.write(writer)?;
witness_script.write(writer)?;
to_self_delay.write(writer)?;
output.write(writer)?;
},
&SpendableOutputDescriptor::DynamicOutputP2WPKH { ref outpoint, ref key, ref output } => {
&SpendableOutputDescriptor::DynamicOutputP2WPKH { ref outpoint, ref per_commitment_point, ref output } => {
2u8.write(writer)?;
outpoint.write(writer)?;
key.write(writer)?;
per_commitment_point.write(writer)?;
output.write(writer)?;
},
}
Expand All @@ -125,14 +127,14 @@ impl Readable for SpendableOutputDescriptor {
}),
1u8 => Ok(SpendableOutputDescriptor::DynamicOutputP2WSH {
outpoint: Readable::read(reader)?,
key: Readable::read(reader)?,
per_commitment_point: Readable::read(reader)?,
witness_script: Readable::read(reader)?,
to_self_delay: Readable::read(reader)?,
output: Readable::read(reader)?,
}),
2u8 => Ok(SpendableOutputDescriptor::DynamicOutputP2WPKH {
outpoint: Readable::read(reader)?,
key: Readable::read(reader)?,
per_commitment_point: Readable::read(reader)?,
output: Readable::read(reader)?,
}),
_ => Err(DecodeError::InvalidValue),
Expand Down Expand Up @@ -245,12 +247,24 @@ 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>>, ()>;

/// Signs a justice transaction.
fn sign_justice_transaction<T: secp256k1::Signing>(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1<T>);

/// Signs a remote htlc transaction.
fn sign_remote_htlc_transaction<T: secp256k1::Signing>(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option<PaymentPreimage>, secp_ctx: &Secp256k1<T>);

/// Create a signature for a (proposed) closing transaction.
///
/// Note that, due to rounding, there may be one "missing" satoshi, and either party may have
/// chosen to forgo their output as dust.
fn sign_closing_transaction<T: secp256k1::Signing>(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;

/// Create a signature for a delayed transaction.
fn sign_delayed_transaction<T: secp256k1::Signing>(&self, spend_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, secp_ctx: &Secp256k1<T>);

/// Create a signture for a payment transaction spending a to_remote output on a remote commitment tx.
fn sign_payment_transaction<T: secp256k1::Signing>(&self, spend_tx: &mut Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, network: Network, secp_ctx: &Secp256k1<T>);

/// Signs a channel announcement message with our funding key, proving it comes from one
/// of the channel participants.
///
Expand Down Expand Up @@ -393,6 +407,38 @@ 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, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_key: &SecretKey, revocation_pubkey: &PublicKey, is_htlc: bool, secp_ctx: &Secp256k1<T>) {
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(&bumped_tx);
let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[input], &witness_script, amount)[..]);
let sig = secp_ctx.sign(&sighash, &revocation_key);
bumped_tx.input[input].witness.push(sig.serialize_der().to_vec());
bumped_tx.input[input].witness[0].push(SigHashType::All as u8);
if is_htlc {
bumped_tx.input[input].witness.push(revocation_pubkey.clone().serialize().to_vec());
} else {
bumped_tx.input[input].witness.push(vec!(1));
}
bumped_tx.input[input].witness.push(witness_script.clone().into_bytes());
}
}

fn sign_remote_htlc_transaction<T: secp256k1::Signing>(&self, bumped_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, preimage: &Option<PaymentPreimage>, secp_ctx: &Secp256k1<T>) {
if let Ok(htlc_key) = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.htlc_base_key) {
let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[input], &witness_script, amount)[..]);
let sig = secp_ctx.sign(&sighash, &htlc_key);
bumped_tx.input[input].witness.push(sig.serialize_der().to_vec());
bumped_tx.input[input].witness[0].push(SigHashType::All as u8);
if let &Some(preimage) = preimage {
bumped_tx.input[input].witness.push(preimage.0.to_vec());
} else {
bumped_tx.input[input].witness.push(vec![0]);
}
bumped_tx.input[input].witness.push(witness_script.clone().into_bytes());
}
}

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(()); }
Expand All @@ -407,6 +453,31 @@ impl ChannelKeys for InMemoryChannelKeys {
Ok(secp_ctx.sign(&sighash, &self.funding_key))
}

fn sign_delayed_transaction<T: secp256k1::Signing>(&self, spend_tx: &mut Transaction, input: usize, witness_script: &Script, amount: u64, per_commitment_point: &PublicKey, secp_ctx: &Secp256k1<T>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the witness_script could be derived here rather than passed in, if I'm not mistaken

if let Ok(htlc_key) = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.delayed_payment_base_key) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

failing here likely indicates a serious internal error, so should propagate the error to the caller

let sighash_parts = bip143::SighashComponents::new(&spend_tx);
let sighash = hash_to_message!(&sighash_parts.sighash_all(&spend_tx.input[input], witness_script, amount)[..]);
let local_delaysig = secp_ctx.sign(&sighash, &htlc_key);
spend_tx.input[0].witness.push(local_delaysig.serialize_der().to_vec());
spend_tx.input[0].witness[0].push(SigHashType::All as u8);
spend_tx.input[0].witness.push(vec!(0));
spend_tx.input[0].witness.push(witness_script.clone().into_bytes());
}
}

fn sign_payment_transaction<T: secp256k1::Signing>(&self, spend_tx: &mut Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, network: Network, secp_ctx: &Secp256k1<T>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see two comments on sign_delayed_transaction

if let Ok(payment_key) = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.payment_base_key) {
let remotepubkey = PublicKey::from_secret_key(&secp_ctx, &payment_key);
let witness_script = Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: remotepubkey}, network).script_pubkey();
let sighash_parts = bip143::SighashComponents::new(&spend_tx);
let sighash = hash_to_message!(&sighash_parts.sighash_all(&spend_tx.input[input], &witness_script, amount)[..]);
let remotesig = secp_ctx.sign(&sighash, &payment_key);
spend_tx.input[0].witness.push(remotesig.serialize_der().to_vec());
spend_tx.input[0].witness[0].push(SigHashType::All as u8);
spend_tx.input[0].witness.push(remotepubkey.serialize().to_vec());
}
}

fn sign_channel_announcement<T: secp256k1::Signing>(&self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {
let msghash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
Ok(secp_ctx.sign(&msghash, &self.funding_key))
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,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);

Expand Down
Loading