-
Notifications
You must be signed in to change notification settings - Fork 407
Clean up ChannelKeys API #598
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
Clean up ChannelKeys API #598
Conversation
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.
Reviewed until 2c09663
// Used to detect programming bug due to unsafe monitor update sequence { ChannelForceClosed, LatestLocalCommitmentTXInfo } | ||
// This is set when the Channel[Manager] generated a ChannelMonitorUpdate which indicated the | ||
// channel has been force-closed. After this is set, no further local commitment transaction | ||
// updates may occur, and we panic!() if one is provided. |
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.
Is this making an assumption we receive ordered ChannelMonitorUpdateStep ? Like what if ChannelManager sends LatestLocalCommitmentTXInfo,ChannelForceClosed but we receive ChannelForceClosed,LatestLocalCommitmentTXInfo ? It should be mostly for all Internet deployment but as we are also targeting support of non-internet stack, for people who want to connect through some poorly-designed hardware link layer, we should care.
Let's avoid to burn our brains on this right now, but stay aware of this.
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.
We always make the assumption that we receive the steps in order. The ChannelMonitorUpdates have serial numbers and the documentation clearly says they have to be in-order, and the ChannelMonitorUpdateSteps are private.
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.
Oh right, it's in ChannelMonitorUpdate
/// Create a signature for a local commitment transaction | ||
/// Create a signature for a local commitment transaction. This will only ever be called with | ||
/// the same local_commitment_tx (or a copy thereof), though there are currently no guarantees | ||
/// that it will not be called multiple times. |
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.
I agree, I think that's good to say you may want to resign multiples times, even the same commitment transaction, like if you do a CPFP, you may want to verify every time the transaction chain back to the funding_outpoint, and therefore redoing the whole signing process.
/// | ||
/// Funding key is your key included in the 2-2 funding_outpoint lock. Should be provided | ||
/// by your ChannelKeys. | ||
/// Funding redeemscript is script locking funding_outpoint. This is the mutlsig script | ||
/// between your own funding key and your counterparty's. Currently, this is provided in | ||
/// ChannelKeys::sign_local_commitment() calls directly. | ||
/// Channel value is amount locked in funding_outpoint. | ||
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; } | ||
pub fn get_local_sig<T: secp256k1::Signing>(&self, funding_key: &SecretKey, funding_redeemscript: &Script, channel_value_satoshis: u64, secp_ctx: &Secp256k1<T>) -> Signature { |
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.
At this commit, can be pub(crate)
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.
This is used by any implementer of ChannelKeys, so I don't think so? I mean we should probably drop it wholesale because just writing the two lines in any implementer of ChannelKeys is better, but...
@@ -422,6 +422,7 @@ pub(crate) enum InputMaterial { | |||
}, | |||
Funding { | |||
channel_value: u64, | |||
funding_redeemscript: Script, | |||
} |
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.
We do store local_channel_pubkeys+remote_channel_pubkeys in InMemoryChannelKeys
, can't you derive script there instead ?
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.
See-also #606.
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.
Hmm, we don't expose the remote_channel_pubkeys in ChannelKeys, nor do I really think we should. We could maybe move to caching that in the ChannelMonitor and passing it in here, but for now might as well just leave it as caching the script.
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.
I pretty agree with other lines in #606, for now I think we can cache the script. I expect signer to verify remote keys, so why caching in ChannelMonitor?
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.
Ehh, I was just thinking try to be less prescriptive on the signer, who knows the signer may be purely remote/hardware/etc, so no need to be too strict about what it provides.
cc7bc39
to
6191a40
Compare
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.
My only concern by batch-siginng HTLC is to make it harder to use SIGHASH_SINGLE for them in the future.
lightning/src/ln/chan_utils.rs
Outdated
if this_htlc.0.transaction_output_index.is_some() { | ||
let htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, local_csv, &this_htlc.0, &self.local_keys.a_delayed_payment_key, &self.local_keys.revocation_key); | ||
assert_eq!(htlc_tx.input.len(), 1); | ||
assert_eq!(htlc_tx.input[0].witness.len(), 0); |
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.
You can assert nSequence==0 and len(Output)=1
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.
I mean I just left these in cause they were already there, hopefully we can scroll up 20 lines and read build_htlc_transaction to see that that is true :). Instead I'm gonna add a commit to drop these.
lightning/src/ln/chan_utils.rs
Outdated
|
||
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.local_keys.a_htlc_key, &self.local_keys.b_htlc_key, &self.local_keys.revocation_key); | ||
|
||
htlc_tx.input[0].witness.push(Vec::new()); // First is the multisig dummy |
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.
Can you add a reference to BIP147, underscores that's a MUST here and we should assert at the end than NULLDUMMY is still one
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.
I don't think we need to assert that something is trivially true. But I added a comment noting the requirement, thanks!
@@ -771,11 +817,47 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> { | |||
if let Some(ref local_commitment) = self.local_commitment { | |||
if local_commitment.has_local_sig() { return Err(()) } | |||
} | |||
if self.local_htlc_sigs.is_some() || self.prev_local_htlc_sigs.is_some() { |
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.
You may extend comment above, if we do have htlc_sigs, that's an hint we have seen a local onchain, even if it's coming from us, we should deny access to commitment (it's say elsewhere also)
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.
Isn't that what it implies with "local comitment transaction has been signed"? I'm a bit confused by your comment here.
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.
Nevermind, I think it was to make the distinction between a "local commitment has been signed" and a "local commitment has been by-this-monitor". But if you see a local commitment onchain, even broadcast from another monitor, the "local" copy should have been signed & broadcast because trigger condition should be met too (aka block height).
We should care about "local" (not remote commitment) and "local"(aka on this watchtower).
} | ||
} | ||
None | ||
if self.prev_local_commitment.is_some() { |
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.
Hmmm is this correct ? If both local_commitment and prev_local_commitment are present, you're going to return a HTLC-transaction for a previous local commtiment while you may have onchain latest local commitment and so return an unspendable transaction ?
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.
In both cases we don't do anything uness the outpoint points to the expected txid, so it should be fine?
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.
Right we check by txid, everything is safe!
Hmm, I hope not, the function could just return a Vec of Transactions or so instead. The API is deliberately somewhat restrictive in that you definitely only ever actually want to sign all the HTLCs at once, so the API matches the safety requirement. |
6191a40
to
fff4b23
Compare
As channel_value last usage was for computing feerate but as this one is static per-commitment and will always-be following specification, we remove it.
In e46e183 we began tracking whether a local commitment transaction had been signed and broadcast in OnchainTxHandler, refusing to update the local commitment transaction state in the ChannelMonitor on that basis. This is fine, except that it doesn't make a lot of sense to store the full local transaction state in OnchainTxHandler - we should be providing it the unsigned local transaction at the time we wish to broadcast and no more (just like we do all other transaction data).
This cleans up sign_local_commitment somewhat by returning a Result<Signaure, ()> over the local commitment transaction instead of modifying the struct which was passed in. This is the first step in making LocalCommitmentTransaction a completely pub struct, using it just to communicate enough information to the user to allow them to construct a signaure instead of having it contain a bunch of logic. This should make it much easier to implement a custom ChannelKeys by disconnecting the local commitment transaction signing from our own datastructures.
1107ab0 introduced an API to have a ChannelKeys implementer sign HTLC transactions by calling into the LocalCommitmentTransaction object, which would then store the tx. This API was incredibly awkward, both because it required an external signer trust our own internal interfaces, but also because it didn't allow for any inspection of what was about to be signed. Further, it signed the HTLC transactions one-by-one in a somewhat inefficient way, and there isn't a clear way to resolve this (as the which-HTLC parameter has to refer to something in between the HTLC's arbitrary index, and its index in the commitment tx, which has "holes" for the non-HTLC outputs and skips some HTLCs). We replace it with a new function in ChannelKeys which allows us to sign all HTLCs in a given commitment transaction (which allows for a bit more effeciency on the signers' part, as well as sidesteps the which-HTLC issue). This may also simplify the signer implementation as we will always want to sign all HTLCs spending a given commitment transaction at once anyway. We also de-mut the LocalCommitmentTransaction passed to the ChanKeys, instead opting to make LocalCommitmentTransaction const and avoid storing any new HTLC-related data in it.
Instead of adding signatures to LocalCommitmentTransactions, we instead leave them unsigned and use them to construct signed Transactions when we want them. This cleans up the guts of LocalCommitmentTransaction enough that we can, and do, expose its state to the world, allowing external signers to have a basic awareness of what they're signing.
We should never be exposing our own TODOs to the world.
We don't need to assert that transaction structure is what we expect when the transaction is created by a function twenty lines up in the same file.
fff4b23
to
03316cd
Compare
Codecov Report
@@ Coverage Diff @@
## master #598 +/- ##
==========================================
+ Coverage 91.05% 91.07% +0.02%
==========================================
Files 34 34
Lines 20359 20447 +88
==========================================
+ Hits 18538 18623 +85
- Misses 1821 1824 +3
Continue to review full report at Codecov.
|
ACK 03316cd |
Based on #597, this cleans up the ChannelKeys API a bunch, closing #567. I'm not convinced the internal interfaces are quite where they need to be, but its a bit better than it was, and we can improve it as we move all signing external.