-
Notifications
You must be signed in to change notification settings - Fork 137
[custom channels]: generate unique script keys for HTLCs #1209
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
e6ccdc5
52ee518
cfc459c
9b9e4aa
2ee822a
3451af2
38b28f2
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 |
|---|---|---|
|
|
@@ -3,12 +3,15 @@ package tapchannel | |
| import ( | ||
| "bytes" | ||
| "fmt" | ||
| "math" | ||
| "math/big" | ||
| "sync" | ||
|
|
||
| "github.com/btcsuite/btcd/btcec/v2" | ||
| "github.com/btcsuite/btcd/btcutil/psbt" | ||
| "github.com/btcsuite/btcd/txscript" | ||
| "github.com/btcsuite/btcd/wire" | ||
| "github.com/decred/dcrd/dcrec/secp256k1/v4" | ||
| "github.com/lightninglabs/taproot-assets/address" | ||
| "github.com/lightninglabs/taproot-assets/asset" | ||
| "github.com/lightninglabs/taproot-assets/commitment" | ||
|
|
@@ -367,7 +370,7 @@ func verifyHtlcSignature(chainParams *address.ChainParams, | |
|
|
||
| vPackets, err := htlcSecondLevelPacketsFromCommit( | ||
| chainParams, chanState, commitTx, baseJob.KeyRing, htlcOutputs, | ||
| baseJob, htlcTimeout, | ||
| baseJob, htlcTimeout, baseJob.HTLC.HtlcIndex, | ||
| ) | ||
| if err != nil { | ||
| return fmt.Errorf("error generating second level packets: %w", | ||
|
|
@@ -511,7 +514,7 @@ func (s *AuxLeafSigner) generateHtlcSignature(chanState lnwallet.AuxChanState, | |
|
|
||
| vPackets, err := htlcSecondLevelPacketsFromCommit( | ||
| s.cfg.ChainParams, chanState, commitTx, baseJob.KeyRing, | ||
| htlcOutputs, baseJob, htlcTimeout, | ||
| htlcOutputs, baseJob, htlcTimeout, baseJob.HTLC.HtlcIndex, | ||
| ) | ||
| if err != nil { | ||
| return lnwallet.AuxSigJobResp{}, fmt.Errorf("error generating "+ | ||
|
|
@@ -599,12 +602,12 @@ func (s *AuxLeafSigner) generateHtlcSignature(chanState lnwallet.AuxChanState, | |
| func htlcSecondLevelPacketsFromCommit(chainParams *address.ChainParams, | ||
| chanState lnwallet.AuxChanState, commitTx *wire.MsgTx, | ||
| keyRing lnwallet.CommitmentKeyRing, htlcOutputs []*cmsg.AssetOutput, | ||
| baseJob lnwallet.BaseAuxJob, | ||
| htlcTimeout fn.Option[uint32]) ([]*tappsbt.VPacket, error) { | ||
| baseJob lnwallet.BaseAuxJob, htlcTimeout fn.Option[uint32], | ||
| htlcIndex uint64) ([]*tappsbt.VPacket, error) { | ||
|
|
||
| packets, _, err := CreateSecondLevelHtlcPackets( | ||
| chanState, commitTx, baseJob.HTLC.Amount.ToSatoshis(), | ||
| keyRing, chainParams, htlcOutputs, htlcTimeout, | ||
| keyRing, chainParams, htlcOutputs, htlcTimeout, htlcIndex, | ||
| ) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("error creating second level HTLC "+ | ||
|
|
@@ -751,3 +754,108 @@ func (v *schnorrSigValidator) validateSchnorrSig(virtualTx *wire.MsgTx, | |
|
|
||
| return nil | ||
| } | ||
|
|
||
| // ScriptKeyTweakFromHtlcIndex converts the given HTLC index into a modulo N | ||
| // scalar that can be used to tweak the internal key of the HTLC script key on | ||
| // the asset level. The value of 1 is always added to the index to make sure | ||
| // this value is always non-zero. | ||
| func ScriptKeyTweakFromHtlcIndex(index input.HtlcIndex) *secp256k1.ModNScalar { | ||
| // If we're at math.MaxUint64, we'd wrap around to 0 if we incremented | ||
| // by 1, but 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 == math.MaxUint64 { | ||
| return new(secp256k1.ModNScalar).SetInt(1) | ||
| } | ||
|
|
||
| // We need to avoid the tweak being zero, so we always add 1 to the | ||
| // index. Otherwise, we'd multiply G by zero. | ||
| index++ | ||
|
|
||
| indexAsBytes := new(big.Int).SetUint64(index).Bytes() | ||
| indexAsScalar := new(secp256k1.ModNScalar) | ||
| _ = indexAsScalar.SetByteSlice(indexAsBytes) | ||
|
|
||
| return indexAsScalar | ||
| } | ||
|
|
||
| // TweakPubKeyWithIndex tweaks the given internal public key with the given | ||
| // HTLC index. The tweak is derived from the index in a way that never results | ||
| // in a zero tweak. The value of 1 is always added to the index to make sure | ||
| // this value is always non-zero. The public key is tweaked like this: | ||
| // | ||
| // tweakedKey = key + (index+1) * G | ||
| func TweakPubKeyWithIndex(pubKey *btcec.PublicKey, | ||
| index input.HtlcIndex) *btcec.PublicKey { | ||
|
Comment on lines
+789
to
+790
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 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 commentThe 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. |
||
|
|
||
| // Avoid panic if input is nil. | ||
| if pubKey == nil { | ||
| return nil | ||
| } | ||
|
|
||
| // We need to operate on Jacobian points, which is just a different | ||
| // representation of the public key that allows us to do scalar | ||
| // multiplication. | ||
| var ( | ||
| pubKeyJacobian, tweakTimesG, tweakedKey btcec.JacobianPoint | ||
| ) | ||
| pubKey.AsJacobian(&pubKeyJacobian) | ||
|
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. nit: ensure that |
||
|
|
||
| // Derive the tweak from the HTLC index in a way that never results in | ||
| // a zero tweak. Then we multiply G by the tweak. | ||
| tweak := ScriptKeyTweakFromHtlcIndex(index) | ||
| secp256k1.ScalarBaseMultNonConst(tweak, &tweakTimesG) | ||
|
|
||
| // And finally we add the result to the key to get the tweaked key. | ||
| secp256k1.AddNonConst(&pubKeyJacobian, &tweakTimesG, &tweakedKey) | ||
|
|
||
| // 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 commentThe reason will be displayed to describe this comment to others. Learn more. From the godoc of 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 commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we assume the input key is on the curve. |
||
| } | ||
|
|
||
| // TweakHtlcTree tweaks the internal key of the given HTLC script tree with the | ||
| // given index, then returns the tweaked tree with the updated taproot key. | ||
| // The tapscript tree and tapscript root are not modified. | ||
| // The internal key is tweaked like this: | ||
| // | ||
| // tweakedInternalKey = internalKey + (index+1) * G | ||
| func TweakHtlcTree(tree input.ScriptTree, | ||
| index input.HtlcIndex) input.ScriptTree { | ||
|
|
||
| // The tapscript tree and root are not modified, only the internal key | ||
| // is tweaked, which inherently modifies the taproot key. | ||
| tweakedInternalPubKey := TweakPubKeyWithIndex(tree.InternalKey, index) | ||
| newTaprootKey := txscript.ComputeTaprootOutputKey( | ||
| tweakedInternalPubKey, tree.TapscriptRoot, | ||
| ) | ||
|
|
||
| return input.ScriptTree{ | ||
| InternalKey: tweakedInternalPubKey, | ||
| TaprootKey: newTaprootKey, | ||
| TapscriptTree: tree.TapscriptTree, | ||
| TapscriptRoot: tree.TapscriptRoot, | ||
| } | ||
| } | ||
|
|
||
| // AddTweakWithIndex adds the given index to the given tweak. If the tweak is | ||
| // empty, the index is used as the tweak directly. The value of 1 is always | ||
| // added to the index to make sure this value is always non-zero. | ||
| func AddTweakWithIndex(maybeTweak []byte, index input.HtlcIndex) []byte { | ||
gijswijs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| indexTweak := ScriptKeyTweakFromHtlcIndex(index) | ||
|
|
||
| // If we don't already have a tweak, we just use the index as the tweak. | ||
| if len(maybeTweak) == 0 { | ||
| return fn.ByteSlice(indexTweak.Bytes()) | ||
| } | ||
|
|
||
| // If we have a tweak, we need to parse/decode it as a scalar, then add | ||
| // the index as a scalar, and encode it back to a byte slice. | ||
| tweak := new(secp256k1.ModNScalar) | ||
| _ = tweak.SetByteSlice(maybeTweak) | ||
| newTweak := tweak.Add(indexTweak) | ||
|
|
||
| return fn.ByteSlice(newTweak.Bytes()) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.