@@ -3,12 +3,15 @@ package tapchannel
33import (
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