Skip to content

Commit

Permalink
tapchannel: add HTLC index tweak to sign desc tweak
Browse files Browse the repository at this point in the history
If we're signing a first-level HTLC leaf, we need to add the HTLC index
tweak to the single tweak.
  • Loading branch information
guggero committed Nov 22, 2024
1 parent 93d4df8 commit 6a09d43
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 10 deletions.
52 changes: 44 additions & 8 deletions tapchannel/aux_leaf_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,20 @@ func verifyHtlcSignature(chainParams *address.ChainParams,
"verify second level: %w", err)
}

// Because we're verifying an HTLC first-level output, we need
// to apply the same tweak to the key as we did when creating
// the script key in the allocation. That tweak was applied to
// the script key to guarantee uniqueness on the asset level.
verifyKey := TweakPubKeyWithIndex(
keyRing.RemoteHtlcKey, baseJob.HTLC.HtlcIndex,
)

leafToVerify := txscript.TapLeaf{
Script: htlcScript.WitnessScriptToSign(),
LeafVersion: txscript.BaseLeafVersion,
}
validator := &schnorrSigValidator{
pubKey: *keyRing.RemoteHtlcKey,
pubKey: *verifyKey,
tapLeaf: lfn.Some(leafToVerify),
signMethod: input.TaprootScriptSpendSignMethod,
}
Expand All @@ -439,8 +447,9 @@ func verifyHtlcSignature(chainParams *address.ChainParams,
// that should be used to verify the generated signature, and also the leaf to
// be signed.
func applySignDescToVIn(signDesc input.SignDescriptor, vIn *tappsbt.VInput,
chainParams *address.ChainParams,
tapscriptRoot []byte) (btcec.PublicKey, txscript.TapLeaf) {
chainParams *address.ChainParams, tapscriptRoot []byte,
index input.HtlcIndex, tweakWithIndex bool) (btcec.PublicKey,
txscript.TapLeaf) {

leafToSign := txscript.TapLeaf{
Script: signDesc.WitnessScript,
Expand All @@ -466,20 +475,26 @@ func applySignDescToVIn(signDesc input.SignDescriptor, vIn *tappsbt.VInput,
vIn.SighashType = signDesc.HashType
vIn.TaprootMerkleRoot = tapscriptRoot

// We might need to apply another tweak to the public key if this is an
// HTLC first-level output, where we've applied a tweak to the script
// key to guarantee uniqueness.
singleTweak := signDesc.SingleTweak
if tweakWithIndex {
singleTweak = AddTweakWithIndex(singleTweak, index)
}

// Apply single or double tweaks if present in the sign
// descriptor. At the same time, we apply the tweaks to a copy
// of the public key, so we can validate the produced signature.
signingKey := signDesc.KeyDesc.PubKey
if len(signDesc.SingleTweak) > 0 {
if len(singleTweak) > 0 {
key := btcwallet.PsbtKeyTypeInputSignatureTweakSingle
vIn.Unknowns = append(vIn.Unknowns, &psbt.Unknown{
Key: key,
Value: signDesc.SingleTweak,
Value: singleTweak,
})

signingKey = input.TweakPubKeyWithTweak(
signingKey, signDesc.SingleTweak,
)
signingKey = input.TweakPubKeyWithTweak(signingKey, singleTweak)
}
if signDesc.DoubleTweak != nil {
key := btcwallet.PsbtKeyTypeInputSignatureTweakDouble
Expand Down Expand Up @@ -542,6 +557,7 @@ func (s *AuxLeafSigner) generateHtlcSignature(chanState lnwallet.AuxChanState,

signingKey, leafToSign := applySignDescToVIn(
signDesc, vIn, s.cfg.ChainParams, tapscriptRoot,
baseJob.HTLC.HtlcIndex, true,
)

// We can now sign this virtual packet, as we've given the
Expand Down Expand Up @@ -832,3 +848,23 @@ func TweakHtlcTree(tree input.ScriptTree,
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 {
indexTweak := HtlcIndexAsScriptKeyTweak(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())
}
60 changes: 60 additions & 0 deletions tapchannel/aux_leaf_signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (
"encoding/hex"
"fmt"
"math"
"math/big"
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/txscript"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/test"
cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg"
"github.com/lightningnetwork/lnd/input"
Expand Down Expand Up @@ -394,3 +396,61 @@ func TestTweakHtlcTree(t *testing.T) {
})
}
}

// TestAddTweakWithIndex tests the AddTweakWithIndex function.
func TestAddTweakWithIndex(t *testing.T) {
var (
bufMaxUint64 = make([]byte, 8)
maxUint64 = new(secp256k1.ModNScalar)
)
binary.BigEndian.PutUint64(bufMaxUint64, math.MaxUint64)
_ = maxUint64.SetByteSlice(bufMaxUint64)
maxUint64Double := new(secp256k1.ModNScalar).
Set(maxUint64).Add(maxUint64)

testCases := []struct {
name string
tweak []byte
index uint64
result *secp256k1.ModNScalar
}{
{
name: "empty tweak, index 0",
index: 0,
result: new(secp256k1.ModNScalar).SetInt(1),
},
{
name: "five as tweak, index 123",
tweak: []byte{0x05},
index: 123,
result: new(secp256k1.ModNScalar).SetInt(129),
},
{
name: "all zero tweak, index 123",
tweak: bytes.Repeat([]byte{0}, 32),
index: 123,
result: new(secp256k1.ModNScalar).SetInt(124),
},
{
name: "tweak math.MaxUint64, index math.MaxUint64-1",
tweak: fn.ByteSlice(maxUint64.Bytes()),
index: math.MaxUint64 - 1,
result: maxUint64Double,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tweak := AddTweakWithIndex(tc.tweak, tc.index)
resultBytes := tc.result.Bytes()
resultBigInt := new(big.Int).SetBytes(resultBytes[:])
tweakBigInt := new(big.Int).SetBytes(tweak)
// 36893488147419103230
// 55340232221128654845

require.Equalf(t, resultBytes[:], tweak, "expected: "+
"%s, got: %s", resultBigInt.String(),
tweakBigInt.String())
})
}
}
3 changes: 1 addition & 2 deletions tapchannel/aux_sweeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,7 @@ func (a *AuxSweeper) signSweepVpackets(vPackets []*tappsbt.VPacket,
// tweaks to generate the key we'll use to verify the
// signature.
signingKey, leafToSign := applySignDescToVIn(
signDesc, vIn, &a.cfg.ChainParams,
tapTweak,
signDesc, vIn, &a.cfg.ChainParams, tapTweak, 0, false,
)

// In this case, the witness isn't special, so we'll set the
Expand Down

0 comments on commit 6a09d43

Please sign in to comment.