Skip to content

Commit 0ce755c

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 fc966d3 commit 0ce755c

File tree

3 files changed

+411
-3
lines changed

3 files changed

+411
-3
lines changed

tapchannel/aux_leaf_signer.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package tapchannel
33
import (
44
"bytes"
55
"fmt"
6+
"math/big"
67
"sync"
78

89
"github.com/btcsuite/btcd/btcec/v2"
910
"github.com/btcsuite/btcd/btcutil/psbt"
1011
"github.com/btcsuite/btcd/txscript"
1112
"github.com/btcsuite/btcd/wire"
13+
"github.com/decred/dcrd/dcrec/secp256k1/v4"
1214
"github.com/lightninglabs/taproot-assets/address"
1315
"github.com/lightninglabs/taproot-assets/asset"
1416
"github.com/lightninglabs/taproot-assets/commitment"
@@ -751,3 +753,102 @@ func (v *schnorrSigValidator) validateSchnorrSig(virtualTx *wire.MsgTx,
751753

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

0 commit comments

Comments
 (0)