Skip to content

Commit e6ccdc5

Browse files
committed
tapchannel: tweak HTLC script key internal key to enforce uniqueness
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.
1 parent 7513141 commit e6ccdc5

File tree

3 files changed

+407
-3
lines changed

3 files changed

+407
-3
lines changed

tapchannel/aux_leaf_signer.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package tapchannel
33
import (
44
"bytes"
55
"fmt"
6+
"math"
7+
"math/big"
68
"sync"
79

810
"github.com/btcsuite/btcd/btcec/v2"
911
"github.com/btcsuite/btcd/btcutil/psbt"
1012
"github.com/btcsuite/btcd/txscript"
1113
"github.com/btcsuite/btcd/wire"
14+
"github.com/decred/dcrd/dcrec/secp256k1/v4"
1215
"github.com/lightninglabs/taproot-assets/address"
1316
"github.com/lightninglabs/taproot-assets/asset"
1417
"github.com/lightninglabs/taproot-assets/commitment"
@@ -751,3 +754,108 @@ func (v *schnorrSigValidator) validateSchnorrSig(virtualTx *wire.MsgTx,
751754

752755
return nil
753756
}
757+
758+
// ScriptKeyTweakFromHtlcIndex converts the given HTLC index into a modulo N
759+
// scalar that can be used to tweak the internal key of the HTLC script key on
760+
// the asset level. The value of 1 is always added to the index to make sure
761+
// this value is always non-zero.
762+
func ScriptKeyTweakFromHtlcIndex(index input.HtlcIndex) *secp256k1.ModNScalar {
763+
// If we're at math.MaxUint64, we'd wrap around to 0 if we incremented
764+
// by 1, but we need to make sure the tweak is 1 to not cause a
765+
// multiplication by zero. This should never happen, as it would mean we
766+
// have more than math.MaxUint64 updates in a channel, which exceeds the
767+
// protocol's maximum.
768+
if index == math.MaxUint64 {
769+
return new(secp256k1.ModNScalar).SetInt(1)
770+
}
771+
772+
// We need to avoid the tweak being zero, so we always add 1 to the
773+
// index. Otherwise, we'd multiply G by zero.
774+
index++
775+
776+
indexAsBytes := new(big.Int).SetUint64(index).Bytes()
777+
indexAsScalar := new(secp256k1.ModNScalar)
778+
_ = indexAsScalar.SetByteSlice(indexAsBytes)
779+
780+
return indexAsScalar
781+
}
782+
783+
// TweakPubKeyWithIndex tweaks the given internal public key with the given
784+
// HTLC index. The tweak is derived from the index in a way that never results
785+
// in a zero tweak. The value of 1 is always added to the index to make sure
786+
// this value is always non-zero. The public key is tweaked like this:
787+
//
788+
// tweakedKey = key + (index+1) * G
789+
func TweakPubKeyWithIndex(pubKey *btcec.PublicKey,
790+
index input.HtlcIndex) *btcec.PublicKey {
791+
792+
// Avoid panic if input is nil.
793+
if pubKey == nil {
794+
return nil
795+
}
796+
797+
// We need to operate on Jacobian points, which is just a different
798+
// representation of the public key that allows us to do scalar
799+
// multiplication.
800+
var (
801+
pubKeyJacobian, tweakTimesG, tweakedKey btcec.JacobianPoint
802+
)
803+
pubKey.AsJacobian(&pubKeyJacobian)
804+
805+
// Derive the tweak from the HTLC index in a way that never results in
806+
// a zero tweak. Then we multiply G by the tweak.
807+
tweak := ScriptKeyTweakFromHtlcIndex(index)
808+
secp256k1.ScalarBaseMultNonConst(tweak, &tweakTimesG)
809+
810+
// And finally we add the result to the key to get the tweaked key.
811+
secp256k1.AddNonConst(&pubKeyJacobian, &tweakTimesG, &tweakedKey)
812+
813+
// Convert the tweaked key back to an affine point and create a new
814+
// taproot key from it.
815+
tweakedKey.ToAffine()
816+
return btcec.NewPublicKey(&tweakedKey.X, &tweakedKey.Y)
817+
}
818+
819+
// TweakHtlcTree tweaks the internal key of the given HTLC script tree with the
820+
// given index, then returns the tweaked tree with the updated taproot key.
821+
// The tapscript tree and tapscript root are not modified.
822+
// The internal key is tweaked like this:
823+
//
824+
// tweakedInternalKey = internalKey + (index+1) * G
825+
func TweakHtlcTree(tree input.ScriptTree,
826+
index input.HtlcIndex) input.ScriptTree {
827+
828+
// The tapscript tree and root are not modified, only the internal key
829+
// is tweaked, which inherently modifies the taproot key.
830+
tweakedInternalPubKey := TweakPubKeyWithIndex(tree.InternalKey, index)
831+
newTaprootKey := txscript.ComputeTaprootOutputKey(
832+
tweakedInternalPubKey, tree.TapscriptRoot,
833+
)
834+
835+
return input.ScriptTree{
836+
InternalKey: tweakedInternalPubKey,
837+
TaprootKey: newTaprootKey,
838+
TapscriptTree: tree.TapscriptTree,
839+
TapscriptRoot: tree.TapscriptRoot,
840+
}
841+
}
842+
843+
// AddTweakWithIndex adds the given index to the given tweak. If the tweak is
844+
// empty, the index is used as the tweak directly. The value of 1 is always
845+
// added to the index to make sure this value is always non-zero.
846+
func AddTweakWithIndex(maybeTweak []byte, index input.HtlcIndex) []byte {
847+
indexTweak := ScriptKeyTweakFromHtlcIndex(index)
848+
849+
// If we don't already have a tweak, we just use the index as the tweak.
850+
if len(maybeTweak) == 0 {
851+
return fn.ByteSlice(indexTweak.Bytes())
852+
}
853+
854+
// If we have a tweak, we need to parse/decode it as a scalar, then add
855+
// the index as a scalar, and encode it back to a byte slice.
856+
tweak := new(secp256k1.ModNScalar)
857+
_ = tweak.SetByteSlice(maybeTweak)
858+
newTweak := tweak.Add(indexTweak)
859+
860+
return fn.ByteSlice(newTweak.Bytes())
861+
}

0 commit comments

Comments
 (0)