lnwallet: populate resolution blob for incoming+outgoing HTLC resolutions

In this commit, we populate the resolution blobs for the incoming and
outgoing HTLCs. We take care to populate the AuxSigDesc with the correct
information, as we need to pass along the second-level aux signature and
also sign desc along with it.
This commit is contained in:
Olaoluwa Osuntokun 2024-10-15 19:15:43 -07:00
parent 08d0aa2368
commit af60fa0c3a
No known key found for this signature in database
GPG Key ID: 90525F7DEEE0AD86
2 changed files with 276 additions and 52 deletions

View File

@ -4455,7 +4455,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context,
// Next, we'll need to send over any updates we sent as part of
// this new proposed commitment state.
for _, logUpdate := range commitDiff.LogUpdates {
commitUpdates = append(commitUpdates, logUpdate.UpdateMsg)
commitUpdates = append(
commitUpdates, logUpdate.UpdateMsg,
)
}
// If this is a taproot channel, then we need to regenerate the
@ -6706,7 +6708,7 @@ type UnilateralCloseSummary struct {
// happen in case we have lost state) it should be set to an empty struct, in
// which case we will attempt to sweep the non-HTLC output using the passed
// commitPoint.
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen
signer input.Signer, commitSpend *chainntnfs.SpendDetail,
remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey,
leafStore fn.Option[AuxLeafStore],
@ -6750,8 +6752,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel,
chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType,
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitSpend.SpendingTx,
chanState.ChanType, isRemoteInitiator, leaseExpiry,
auxResult.AuxLeaves,
chanState.ChanType, isRemoteInitiator, leaseExpiry, chanState,
auxResult.AuxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to create htlc resolutions: %w",
@ -7042,8 +7044,10 @@ func newOutgoingHtlcResolution(signer input.Signer,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType,
auxLeaves fn.Option[CommitAuxLeaves]) (*OutgoingHtlcResolution, error) {
chanType channeldb.ChannelType, chanState *channeldb.OpenChannel,
auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver],
) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
@ -7074,6 +7078,8 @@ func newOutgoingHtlcResolution(signer input.Signer,
return nil, err
}
htlcCsvDelay := HtlcSecondLevelInputSequence(chanType)
// If we're spending this HTLC output from the remote node's
// commitment, then we won't need to go to the second level as our
// outputs don't have a CSV delay.
@ -7111,11 +7117,43 @@ func newOutgoingHtlcResolution(signer input.Signer,
}
}
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootHtlcOfferedRemoteTimeout,
CloseType: RemoteForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: signDesc,
KeyRing: keyRing,
CsvDelay: htlcCsvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.RemoteCommitment.CommitFee,
HtlcID: fn.Some(htlc.HtlcIndex),
PayHash: fn.Some(htlc.RHash),
}
resolveRes := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: HtlcSecondLevelInputSequence(chanType),
Expiry: htlc.RefundTimeout,
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
}, nil
}
@ -7266,31 +7304,78 @@ func newOutgoingHtlcResolution(signer input.Signer,
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
)
// In addition to the info in txSignDetails, we also need extra
// information to sweep the second level output after confirmation.
sweepSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
Output: &wire.TxOut{
PkScript: htlcSweepScript.PkScript(),
Value: int64(secondLevelOutputAmt),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
htlcSweepScript.PkScript(),
int64(secondLevelOutputAmt),
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
}
// This might be an aux channel, so we'll go ahead and attempt to
// generate the resolution blob for the channel so we can pass along to
// the sweeping sub-system.
resolveRes := fn.MapOptionZ(
auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] {
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:lll
FundingBlob: chanState.CustomBlob,
Type: input.TaprootHtlcLocalOfferedTimeout, //nolint:lll
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: sweepSignDesc,
KeyRing: keyRing,
CsvDelay: htlcCsvDelay,
HtlcAmt: btcutil.Amount(txOut.Value),
CommitCsvDelay: csvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.LocalCommitment.CommitFee, //nolint:lll
HtlcID: fn.Some(htlc.HtlcIndex),
PayHash: fn.Some(htlc.RHash),
AuxSigDesc: fn.Some(AuxSigDesc{
SignDetails: *txSignDetails,
AuxSig: func() []byte {
tlvType := htlcCustomSigType.TypeVal() //nolint:lll
return htlc.CustomRecords[uint64(tlvType)] //nolint:lll
}(),
}),
}
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
SignedTimeoutTx: timeoutTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
ClaimOutpoint: wire.OutPoint{
Hash: timeoutTx.TxHash(),
Index: 0,
},
SweepSignDesc: input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
Output: &wire.TxOut{
PkScript: htlcSweepScript.PkScript(),
Value: int64(secondLevelOutputAmt),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
htlcSweepScript.PkScript(),
int64(secondLevelOutputAmt),
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
},
SweepSignDesc: sweepSignDesc,
}, nil
}
@ -7306,8 +7391,10 @@ func newIncomingHtlcResolution(signer input.Signer,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType,
auxLeaves fn.Option[CommitAuxLeaves]) (*IncomingHtlcResolution, error) {
chanType channeldb.ChannelType, chanState *channeldb.OpenChannel,
auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver],
) (*IncomingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
@ -7339,6 +7426,8 @@ func newIncomingHtlcResolution(signer input.Signer,
return nil, err
}
htlcCsvDelay := HtlcSecondLevelInputSequence(chanType)
// If we're spending this output from the remote node's commitment,
// then we can skip the second layer and spend the output directly.
if whoseCommit.IsRemote() {
@ -7374,10 +7463,44 @@ func newIncomingHtlcResolution(signer input.Signer,
}
}
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
Type: input.TaprootHtlcAcceptedRemoteSuccess,
FundingBlob: chanState.CustomBlob,
CloseType: RemoteForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: signDesc,
KeyRing: keyRing,
HtlcID: fn.Some(htlc.HtlcIndex),
CsvDelay: htlcCsvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.RemoteCommitment.CommitFee,
PayHash: fn.Some(htlc.RHash),
CommitCsvDelay: csvDelay,
HtlcAmt: htlc.Amt.ToSatoshis(),
}
resolveRes := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &IncomingHtlcResolution{
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: HtlcSecondLevelInputSequence(chanType),
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: htlcCsvDelay,
ResolutionBlob: resolutionBlob,
}, nil
}
@ -7523,30 +7646,76 @@ func newIncomingHtlcResolution(signer input.Signer,
localDelayTweak := input.SingleTweakBytes(
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
)
// In addition to the info in txSignDetails, we also need extra
// information to sweep the second level output after confirmation.
sweepSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
Output: &wire.TxOut{
PkScript: htlcSweepScript.PkScript(),
Value: int64(secondLevelOutputAmt),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
htlcSweepScript.PkScript(),
int64(secondLevelOutputAmt),
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
}
resolveRes := fn.MapOptionZ(
auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] {
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:lll
Type: input.TaprootHtlcAcceptedLocalSuccess, //nolint:lll
FundingBlob: chanState.CustomBlob,
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: sweepSignDesc,
KeyRing: keyRing,
HtlcID: fn.Some(htlc.HtlcIndex),
CsvDelay: htlcCsvDelay,
CommitFee: chanState.LocalCommitment.CommitFee, //nolint:lll
PayHash: fn.Some(htlc.RHash),
AuxSigDesc: fn.Some(AuxSigDesc{
SignDetails: *txSignDetails,
AuxSig: func() []byte {
tlvType := htlcCustomSigType.TypeVal() //nolint:lll
return htlc.CustomRecords[uint64(tlvType)] //nolint:lll
}(),
}),
CommitCsvDelay: csvDelay,
HtlcAmt: btcutil.Amount(txOut.Value),
CltvDelay: fn.Some(htlc.RefundTimeout),
}
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.Option()
return &IncomingHtlcResolution{
SignedSuccessTx: successTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
ClaimOutpoint: wire.OutPoint{
Hash: successTx.TxHash(),
Index: 0,
},
SweepSignDesc: input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
Output: &wire.TxOut{
PkScript: htlcSweepScript.PkScript(),
Value: int64(secondLevelOutputAmt),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
htlcSweepScript.PkScript(),
int64(secondLevelOutputAmt),
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
},
SweepSignDesc: sweepSignDesc,
}, nil
}
@ -7583,7 +7752,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
commitTx *wire.MsgTx, chanType channeldb.ChannelType,
isCommitFromInitiator bool, leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*HtlcResolutions, error) {
chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver]) (*HtlcResolutions, error) {
// TODO(roasbeef): don't need to swap csv delay?
dustLimit := remoteChanCfg.DustLimit
@ -7618,7 +7788,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
signer, localChanCfg, commitTx, &htlc,
keyRing, feePerKw, uint32(csvDelay),
leaseExpiry, whoseCommit, isCommitFromInitiator,
chanType, auxLeaves,
chanType, chanState, auxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("incoming resolution "+
@ -7632,7 +7802,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
ohr, err := newOutgoingHtlcResolution(
signer, localChanCfg, commitTx, &htlc, keyRing,
feePerKw, uint32(csvDelay), leaseExpiry, whoseCommit,
isCommitFromInitiator, chanType, auxLeaves,
isCommitFromInitiator, chanType, chanState, auxLeaves,
auxResolver,
)
if err != nil {
return nil, fmt.Errorf("outgoing resolution "+
@ -7884,7 +8055,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
//nolint:lll
return a.ResolveContract(ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanPoint: chanState.FundingOutpoint, //nolint:lll
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob,
@ -7917,7 +8089,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local,
signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitTx, chanState.ChanType,
chanState.IsInitiator, leaseExpiry, auxResult.AuxLeaves,
chanState.IsInitiator, leaseExpiry, chanState,
auxResult.AuxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to gen htlc resolution: %w", err)

51
macaroons/fuzz_test.go Normal file
View File

@ -0,0 +1,51 @@
package macaroons
import (
"context"
"testing"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
func FuzzUnmarshalMacaroon(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
mac := &macaroon.Macaroon{}
_ = mac.UnmarshalBinary(data)
})
}
func FuzzAuthChecker(f *testing.F) {
rootKeyStore := bakery.NewMemRootKeyStore()
ctx := context.Background()
f.Fuzz(func(t *testing.T, location, entity, action, method string,
rootKey, id []byte) {
macService, err := NewService(
rootKeyStore, location, true, IPLockChecker,
)
if err != nil {
return
}
requiredPermissions := []bakery.Op{{
Entity: entity,
Action: action,
}}
mac, err := macaroon.New(rootKey, id, location, macaroon.V2)
if err != nil {
return
}
macBytes, err := mac.MarshalBinary()
if err != nil {
return
}
_ = macService.CheckMacAuth(
ctx, macBytes, requiredPermissions, method,
)
})
}