-
Notifications
You must be signed in to change notification settings - Fork 117
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
[custom channels]: generate unique script keys for HTLCs #1209
Conversation
Pull Request Test Coverage Report for Build 12056907010Details
💛 - Coveralls |
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.
Very nice. I did a thorough review and left a bunch of comments throughout, but no blockers AFAICT.
57ee008
to
1163f5b
Compare
I rebased this on top of #1154 to verify everything works. @Roasbeef I have run a full itest suite on top of this PR to validate it works. |
We are nearly there. Just two minor nits and a comment to check whether I understand the code. |
I could change the base branch, yes. But because I also rebased it, it will still show all commits. So it doesn't really matter that much, this will need to land after #1154 in any case. EDIT: I did change the base branch, since that will avoid accidental merge. |
1163f5b
to
fda054d
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.
LGTM! 🎉
Thanks for the review! Unfortunately while testing this in an itest, I ran into a bit of a road block. I think we might actually need to introduce a stable sort index as a custom records to the HTLC, so both parties can use the correct value when sorting. |
Does the in-place allocation sort need to factor in the htlc index now? Before this change, they were identical for a given |
fda054d
to
c96f369
Compare
c96f369
to
1e85b09
Compare
I think I found and squashed all MPP related bugs now. But I expect the CI to be fully green on this now, including the current |
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.
LGTM! Massive lift 🚀
I think the commit message for the first commit is now wrong btw? Wrt. mentioning that the tweaking for revocation is not implemented.
tapchannel/allocation_sort.go
Outdated
@@ -43,7 +43,7 @@ func (s sortableAllocationSlice) Swap(i, j int) { | |||
} | |||
|
|||
// Less is a modified BIP69 output comparison, that sorts based on value, then | |||
// pkScript, then CLTV value. | |||
// pkScript, then CLTV value and finally HtlcIndex. | |||
// | |||
// NOTE: Part of the sort.Interface interface. | |||
func (s sortableAllocationSlice) Less(i, j int) bool { |
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.
non-blocking nit: AFAICT the stdlib recommendation now is to use slices.SortFunc
instead of sort.Sort
.
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.
Makes sense. Also found a way to simplify the code quite a bit.
// Convert the tweaked key back to an affine point and create a new | ||
// taproot key from it. | ||
tweakedKey.ToAffine() | ||
return btcec.NewPublicKey(&tweakedKey.X, &tweakedKey.Y) |
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.
From the godoc of NewPublicKey
; does this output key need to be checked with IsOnCurve
?
My guess is no since we know the input was on the curve.
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.
Yeah, we assume the input key is on the curve.
tapchannel/aux_leaf_signer.go
Outdated
// We need to avoid the tweak being zero, so we always add 1 to the | ||
// index. Otherwise, we'd multiply G by zero. | ||
index++ | ||
|
||
// If we wrapped around from math.MaxUint64 to 0, we need to make sure | ||
// the tweak is 1 to not cause a multiplication by zero. This should | ||
// never happen, as it would mean we have more than math.MaxUint64 | ||
// updates in a channel, which exceeds the protocol's maximum. | ||
if index == 0 { | ||
return new(secp256k1.ModNScalar).SetInt(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.
To avoid overflowing index
we could do something like this instead:
if index == math.MaxUint64 {
return new(secp256k1.ModNScalar).SetInt(1)
}
index++
...
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.
Also, on the function name, perhaps ScriptKeyTweakFromHtlcIndex
.
var ( | ||
pubKeyJacobian, tweakTimesG, tweakedKey btcec.JacobianPoint | ||
) | ||
pubKey.AsJacobian(&pubKeyJacobian) |
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.
nit: ensure that pubKey
is not nil
.
func TweakPubKeyWithIndex(pubKey *btcec.PublicKey, | ||
index input.HtlcIndex) *btcec.PublicKey { |
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 feel as if this function and the one above should be generalised into "tweak public key by integer" function(s). Which can live in btcec eventually. None of this logic is unique to taproot assets or HTLCs.
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.
Sure, makes sense. Keeping things as they are for now, but definitely something we can refactor later.
No, it's correct. Sweeping an HTLC of a revoked commitment (old state) isn't implemented yet. In that case we would need to tweak the signing key at sign time. In any non-revocation (e.g. normal, latest-state force close), we only need to tweak the internal key when we create the output and then again when we create the control block. But no signing private key needs to be tweaked as of now. |
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.
No further remarks, only questions to see if I understand it. Also the last two commits fix problems that are unrelated to HTLC script key tweak, am I right?
The change output index calculation and the upsert of tweaks are unrelated to the initial problem AFAICT.
tl;dr: LGTM! 🎉
@@ -766,13 +780,13 @@ func CreateAllocations(chanState lnwallet.AuxChanState, ourBalance, | |||
NonAssetLeaves: sibling, | |||
ScriptKey: asset.ScriptKey{ | |||
PubKey: asset.NewScriptKey( | |||
htlcTree.TaprootKey, | |||
tweakedTree.TaprootKey, |
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.
Just to double check: The SortTaprootKeyBytes
should not be converted to tweakedTree.TaprootKey
because that's the output key of the on-chain P2TR output that would be created if there was no asset commitment. So that should remain untweaked?
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, correct. We need to make sure the sort order is stable before applying any tweaks (TAP leaf or HtlcIndex). Added a comment to make this clear.
@@ -1289,12 +1317,12 @@ func createSecondLevelHtlcAllocations(chanType channeldb.ChannelType, | |||
), | |||
InternalKey: htlcTree.InternalKey, | |||
NonAssetLeaves: sibling, | |||
ScriptKey: asset.NewScriptKey(htlcTree.TaprootKey), | |||
ScriptKey: asset.NewScriptKey(tweakedTree.TaprootKey), | |||
SortTaprootKeyBytes: schnorr.SerializePubKey( |
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.
Same question 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.
Added a comment here too.
tapchannel/allocation_sort.go
Outdated
return true | ||
} | ||
|
||
return allocI.HtlcIndex < allocJ.HtlcIndex |
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 commit tweaks the internal key of the asset-level script key with the HTLC index to enforce uniqueness of script keys across multiple HTLCs with the same payment hash and timeout (MPP shards of the same payment). The internal key we tweak is the revocation key. So we must also apply that same tweak when doing a revocation/breach sweep transaction. But since that functionality is not yet implemented, we'll just leave a TODO in a follow-up commit for that. Therefore, no tweak will currently need to be applied at _sign_ time.
This fixes a bug where the order of the HTLCs would not be deterministic between two peers if they were identical due to them being shards of the same MPP payment. By adding the HTLC index as a further sort argument, the order is again stable and deterministic.
This commit adds a couple of trace outputs that were helpful in finding two bugs fixed in this series of commits. We also increase the number of levels the spew logger is allowed to print, so we can see a bit more. We need this limit to avoid endless loops in self-referencing structs such as proofs.
This fixes a bug where we previously assumed there would only ever be one asset input in a sweep. But when we sweep multiple HTLCs at the same time, the change output index isn't static and needs to be calculated based on the asset commitment output indexes.
Correct. But those changes were required to get the integration tests passing, so they were required follow-up fixes. |
1e85b09
to
63fb86c
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.
LGTM 🦋
END | ||
END, | ||
-- If the tweak was previously unknown, we'll update to the new value. | ||
tweak = CASE |
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.
Non-blocking, but can extend the recently added TestScriptKeyKnownUpsert test to cover 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.
Yeah, makes sense. Added.
This fixes a bug where if we ever inserted a script key before knowing its tweak, we could never update the tweak later on when we learn it. This meant that such keys would be seen as BIP-086 keys, even though we later learn they aren't.
63fb86c
to
38b28f2
Compare
Fixes the LiT integration tests that were broken after merging #1181.
But that basically showed us that colliding script keys for MPP shard HTLCs are actually a problem.
This PR tweaks each script key with the HTLC index, making them unique again, even if they have the same payment hash and timeout (which results in the same BTC level taproot output key).
To make the accompanying
litd
integration tests pass, we also fix the following two bugs:UpsertScriptKey
that wouldn't allow the tweak of a script key to be updated when it is learned after the key was already added to the DB.