Skip to content

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

Merged
merged 7 commits into from
Apr 25, 2020

Conversation

TheBlueMatt
Copy link
Collaborator

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.

@TheBlueMatt TheBlueMatt added this to the 0.0.11 milestone Apr 20, 2020
Copy link

@ariard ariard left a 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.
Copy link

Choose a reason for hiding this comment

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

7e11701

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.

Copy link
Collaborator Author

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.

Copy link

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.
Copy link

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 {
Copy link

Choose a reason for hiding this comment

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

2c09663

At this commit, can be pub(crate)

Copy link
Collaborator Author

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,
}
Copy link

Choose a reason for hiding this comment

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

2c09663

We do store local_channel_pubkeys+remote_channel_pubkeys in InMemoryChannelKeys, can't you derive script there instead ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See-also #606.

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, 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.

Copy link

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?

Copy link
Collaborator Author

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.

@TheBlueMatt TheBlueMatt force-pushed the 2020-04-559-cleanups branch 3 times, most recently from cc7bc39 to 6191a40 Compare April 23, 2020 21:31
Copy link

@ariard ariard left a 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.

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);
Copy link

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

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 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.


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
Copy link

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

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 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() {
Copy link

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)

Copy link
Collaborator Author

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.

Copy link

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() {
Copy link

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 ?

Copy link
Collaborator Author

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?

Copy link

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!

@TheBlueMatt
Copy link
Collaborator Author

My only concern by batch-siginng HTLC is to make it harder to use SIGHASH_SINGLE for them in the future.s

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.

@TheBlueMatt TheBlueMatt force-pushed the 2020-04-559-cleanups branch from 6191a40 to fff4b23 Compare April 24, 2020 20:03
Antoine Riard and others added 7 commits April 24, 2020 18:51
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.
@TheBlueMatt TheBlueMatt force-pushed the 2020-04-559-cleanups branch from fff4b23 to 03316cd Compare April 25, 2020 01:24
@codecov
Copy link

codecov bot commented Apr 25, 2020

Codecov Report

Merging #598 into master will increase coverage by 0.02%.
The diff coverage is 96.42%.

Impacted file tree graph

@@            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     
Impacted Files Coverage Δ
lightning/src/ln/onchaintx.rs 94.86% <93.69%> (-0.26%) ⬇️
lightning/src/ln/channelmonitor.rs 95.50% <95.34%> (-0.01%) ⬇️
lightning/src/chain/keysinterface.rs 96.98% <100.00%> (-0.07%) ⬇️
lightning/src/ln/chan_utils.rs 97.19% <100.00%> (+0.44%) ⬆️
lightning/src/ln/channel.rs 86.41% <100.00%> (-0.01%) ⬇️
lightning/src/util/enforcing_trait_impls.rs 100.00% <100.00%> (ø)
lightning/src/ln/functional_tests.rs 97.02% <0.00%> (-0.02%) ⬇️
lightning/src/util/ser_macros.rs 97.27% <0.00%> (+0.68%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4dc0dd1...03316cd. Read the comment docs.

@TheBlueMatt TheBlueMatt mentioned this pull request Apr 25, 2020
@ariard
Copy link

ariard commented Apr 25, 2020

ACK 03316cd

@TheBlueMatt TheBlueMatt merged commit 8fb50f2 into lightningdevkit:master Apr 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants