Skip to content

Remove signing from Channel #420

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ pub fn do_test(data: &[u8]) {
monitor.latest_good_update.lock().unwrap().insert(outpoint, monitor_ser);
}
let mut monitor_refs = HashMap::new();
for (outpoint, monitor) in monitors.iter() {
for (outpoint, monitor) in monitors.iter_mut() {
monitor_refs.insert(*outpoint, monitor);
}

Expand All @@ -223,7 +223,7 @@ pub fn do_test(data: &[u8]) {
tx_broadcaster: broadcast.clone(),
logger,
default_config: config,
channel_monitors: &monitor_refs,
channel_monitors: &mut monitor_refs,
};

let res = (<(Sha256d, ChannelManager<EnforcingChannelKeys>)>::read(&mut Cursor::new(&$ser.0), read_args).expect("Failed to read manager").1, monitor);
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/chain/keysinterface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub trait ChannelKeys : Send {
/// TODO: Document the things someone using this interface should enforce before signing.
/// TODO: Add more input vars to enable better checking (preferably removing commitment_tx and
/// making the callee generate it via some util function we expose)!
fn sign_remote_commitment<T: secp256k1::Signing>(&self, channel_value_satoshis: u64, channel_funding_redeemscript: &Script, absolute_fee: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()>;
fn sign_remote_commitment<T: secp256k1::Signing>(&self, channel_value_satoshis: u64, channel_funding_redeemscript: &Script, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()>;

/// Create a signature for a (proposed) closing transaction.
///
Expand Down
166 changes: 161 additions & 5 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@

use bitcoin::blockdata::script::{Script,Builder};
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::transaction::{TxIn,TxOut,OutPoint,Transaction};
use bitcoin::blockdata::transaction::{TxIn,TxOut,OutPoint,Transaction, SigHashType};
use bitcoin::consensus::encode::{self, Decodable, Encodable};
use bitcoin::util::bip143;

use bitcoin_hashes::{Hash, HashEngine};
use bitcoin_hashes::sha256::Hash as Sha256;
use bitcoin_hashes::ripemd160::Hash as Ripemd160;
use bitcoin_hashes::hash160::Hash as Hash160;
use bitcoin_hashes::sha256d::Hash as Sha256dHash;

use ln::channelmanager::PaymentHash;
use ln::channelmanager::{PaymentHash, PaymentPreimage};
use ln::msgs::DecodeError;
use util::ser::{Readable, Writeable, Writer, WriterWriteAdaptor};

use secp256k1::key::{PublicKey,SecretKey};
use secp256k1::Secp256k1;
use secp256k1::key::{SecretKey,PublicKey};
use secp256k1::{Secp256k1, Signature};
use secp256k1;

pub(super) const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
Expand Down Expand Up @@ -59,7 +63,9 @@ pub(super) fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>,
base_point.combine(&hashkey)
}

/// Derives a revocation key from its constituent parts
/// 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
Copy link

Choose a reason for hiding this comment

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

9aadb7f

Comment is obscure, is this a mention to the case that you were speaking about with a revocation pubkey without a private key ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Its a key assumption in being able to not derive the private key at commitment-check-time and instead do it at broadcast time.

/// 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> {
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 Expand Up @@ -281,3 +287,153 @@ pub fn build_htlc_transaction(prev_hash: &Sha256dHash, feerate_per_kw: u64, to_s
output: txouts,
}
}

/// Signs a transaction created by build_htlc_transaction. If the transaction is an
/// HTLC-Success transaction (ie htlc.offered is false), preimage must be set!
pub(crate) fn sign_htlc_transaction<T: secp256k1::Signing>(tx: &mut Transaction, their_sig: &Signature, preimage: &Option<PaymentPreimage>, htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey, per_commitment_point: &PublicKey, htlc_base_key: &SecretKey, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Script), ()> {
if tx.input.len() != 1 { return Err(()); }
if tx.input[0].witness.len() != 0 { return Err(()); }

let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&htlc, a_htlc_key, b_htlc_key, revocation_key);

let our_htlc_key = derive_private_key(secp_ctx, per_commitment_point, htlc_base_key).map_err(|_| ())?;
let sighash = hash_to_message!(&bip143::SighashComponents::new(&tx).sighash_all(&tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]);
let local_tx = PublicKey::from_secret_key(&secp_ctx, &our_htlc_key) == *a_htlc_key;
let our_sig = secp_ctx.sign(&sighash, &our_htlc_key);

tx.input[0].witness.push(Vec::new()); // First is the multisig dummy

if local_tx { // b, then a
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
tx.input[0].witness.push(our_sig.serialize_der().to_vec());
} else {
tx.input[0].witness.push(our_sig.serialize_der().to_vec());
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
}
tx.input[0].witness[1].push(SigHashType::All as u8);
tx.input[0].witness[2].push(SigHashType::All as u8);

if htlc.offered {
tx.input[0].witness.push(Vec::new());
assert!(preimage.is_none());
} else {
tx.input[0].witness.push(preimage.unwrap().0.to_vec());
}
Copy link

Choose a reason for hiding this comment

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

9aadb7f

You can add assert!(tx.locktime == htlc.cltv_expiry) for !htlc.offered

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, this appears to fail in 15 tests. I'll leave it for adding later.

Copy link

Choose a reason for hiding this comment

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

Ah yeah it's also used to build remote htlc transaction so we can't rely on offered flag. All tests passed with preimage.is_none { assert!(tx.lock_time == htlc.ctlv_expiry)


tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec());

Ok((our_sig, htlc_redeemscript))
}

#[derive(Clone)]
/// We use this to track local commitment transactions and put off signing them until we are ready
/// to broadcast. Eventually this will require a signer which is possibly external, but for now we
/// just pass in the SecretKeys required.
pub(crate) struct LocalCommitmentTransaction {
tx: Transaction
}
impl LocalCommitmentTransaction {
#[cfg(test)]
pub fn dummy() -> Self {
Self { tx: Transaction {
version: 2,
input: Vec::new(),
output: Vec::new(),
lock_time: 0,
} }
}

pub fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey) -> LocalCommitmentTransaction {
if tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
Copy link

Choose a reason for hiding this comment

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

85b1a9e

Can we extend the number of non contextual commitment transaction format checks ? Like checking locktime and nSequence by passing the obscured commitment number or verifying then every output is either P2WSH or P2WPKH ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I mean this is still an internal function, so these are more to prevent invalid usage of the struct that indicates we've confused ourselves over where the state machine is. Being able to do more is something we should add to EnforcingChannelKeys, but that can come over time once we move the add_local_sig to that.

if tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }

tx.input[0].witness.push(Vec::new()); // First is the multisig dummy

if our_funding_key.serialize()[..] < their_funding_key.serialize()[..] {
tx.input[0].witness.push(Vec::new());
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
tx.input[0].witness[2].push(SigHashType::All as u8);
} else {
tx.input[0].witness.push(their_sig.serialize_der().to_vec());
tx.input[0].witness[1].push(SigHashType::All as u8);
tx.input[0].witness.push(Vec::new());
}

Self { tx }
}

pub fn txid(&self) -> Sha256dHash {
self.tx.txid()
}

pub fn has_local_sig(&self) -> bool {
if self.tx.input.len() != 1 { panic!("Commitment transactions must have input count == 1!"); }
if self.tx.input[0].witness.len() == 4 {
assert!(!self.tx.input[0].witness[1].is_empty());
assert!(!self.tx.input[0].witness[2].is_empty());
true
} else {
assert_eq!(self.tx.input[0].witness.len(), 3);
assert!(self.tx.input[0].witness[0].is_empty());
assert!(self.tx.input[0].witness[1].is_empty() || self.tx.input[0].witness[2].is_empty());
false
}
}

pub fn add_local_sig<T: secp256k1::Signing>(&mut self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) {
if self.has_local_sig() { return; }
let sighash = hash_to_message!(&bip143::SighashComponents::new(&self.tx)
.sighash_all(&self.tx.input[0], funding_redeemscript, channel_value_satoshis)[..]);
let our_sig = secp_ctx.sign(&sighash, funding_key);

if self.tx.input[0].witness[1].is_empty() {
self.tx.input[0].witness[1] = our_sig.serialize_der().to_vec();
self.tx.input[0].witness[1].push(SigHashType::All as u8);
} else {
self.tx.input[0].witness[2] = our_sig.serialize_der().to_vec();
self.tx.input[0].witness[2].push(SigHashType::All as u8);
}

self.tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec());
}

pub fn without_valid_witness(&self) -> &Transaction { &self.tx }
pub fn with_valid_witness(&self) -> &Transaction {
assert!(self.has_local_sig());
&self.tx
}
}
impl PartialEq for LocalCommitmentTransaction {
// We dont care whether we are signed in equality comparison
fn eq(&self, o: &Self) -> bool {
self.txid() == o.txid()
}
}
impl Writeable for LocalCommitmentTransaction {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
if let Err(e) = self.tx.consensus_encode(&mut WriterWriteAdaptor(writer)) {
match e {
encode::Error::Io(e) => return Err(e),
_ => panic!("local tx must have been well-formed!"),
}
}
Ok(())
}
}
impl<R: ::std::io::Read> Readable<R> for LocalCommitmentTransaction {
fn read(reader: &mut R) -> Result<Self, DecodeError> {
let tx = match Transaction::consensus_decode(reader.by_ref()) {
Ok(tx) => tx,
Err(e) => match e {
encode::Error::Io(ioe) => return Err(DecodeError::Io(ioe)),
_ => return Err(DecodeError::InvalidValue),
},
};

if tx.input.len() != 1 {
// Ensure tx didn't hit the 0-input ambiguity case.
return Err(DecodeError::InvalidValue);
}
Ok(Self { tx })
}
}
Loading