-
Notifications
You must be signed in to change notification settings - Fork 409
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
Changes from all commits
eeac6c9
33810ed
200e898
eb97a75
e2e1628
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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 | ||
/// 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); | ||
|
@@ -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()); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can add There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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 { | ||
TheBlueMatt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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!"); } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }) | ||
} | ||
} |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.