mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
multi: obtain+verify aux sigs for all second level HTLCs
In this commit, we start to use the new AuxSigner to obtain+verify aux sigs for all second level HTLCs. This is similar to the existing SigPool, but we'll only attempt to do this if the AuxSigner is present (won't be for most channels).
This commit is contained in:
parent
bd84fd256e
commit
83fdbda2fa
@ -68,6 +68,10 @@ type Config struct {
|
||||
// leaves for certain custom channel types.
|
||||
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[lnwallet.AuxSigner]
|
||||
|
||||
// BlockCache is the main cache for storing block information.
|
||||
BlockCache *blockcache.BlockCache
|
||||
|
||||
|
@ -174,6 +174,10 @@ type AuxComponents struct {
|
||||
// able to automatically handle new custom protocol messages related to
|
||||
// the funding process.
|
||||
AuxFundingController fn.Option[funding.AuxFundingController]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[lnwallet.AuxSigner]
|
||||
}
|
||||
|
||||
// DefaultWalletImpl is the default implementation of our normal, btcwallet
|
||||
@ -580,6 +584,7 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
|
||||
ChanStateDB: dbs.ChanStateDB.ChannelStateDB(),
|
||||
NeutrinoCS: neutrinoCS,
|
||||
AuxLeafStore: aux.AuxLeafStore,
|
||||
AuxSigner: aux.AuxSigner,
|
||||
ActiveNetParams: d.cfg.ActiveNetParams,
|
||||
FeeURL: d.cfg.FeeURL,
|
||||
Fee: &lncfg.Fee{
|
||||
@ -737,6 +742,7 @@ func (d *DefaultWalletImpl) BuildChainControl(
|
||||
NetParams: *walletConfig.NetParams,
|
||||
CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
|
||||
AuxLeafStore: partialChainControl.Cfg.AuxLeafStore,
|
||||
AuxSigner: partialChainControl.Cfg.AuxSigner,
|
||||
}
|
||||
|
||||
// The broadcast is already always active for neutrino nodes, so we
|
||||
@ -919,10 +925,6 @@ type DatabaseInstances struct {
|
||||
// for native SQL queries for tables that already support it. This may
|
||||
// be nil if the use-native-sql flag was not set.
|
||||
NativeSQLStore *sqldb.BaseDB
|
||||
|
||||
// AuxLeafStore is an optional data source that can be used by custom
|
||||
// channels to fetch+store various data.
|
||||
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||
}
|
||||
|
||||
// DefaultDatabaseBuilder is a type that builds the default database backends
|
||||
|
@ -2360,9 +2360,12 @@ func createInitChannels(t *testing.T) (
|
||||
)
|
||||
bobSigner := input.NewMockSigner([]*btcec.PrivateKey{bobKeyPriv}, nil)
|
||||
|
||||
signerMock := lnwallet.NewDefaultAuxSignerMock(t)
|
||||
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
||||
channelAlice, err := lnwallet.NewLightningChannel(
|
||||
aliceSigner, aliceChannelState, alicePool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -2375,6 +2378,8 @@ func createInitChannels(t *testing.T) (
|
||||
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
||||
channelBob, err := lnwallet.NewLightningChannel(
|
||||
bobSigner, bobChannelState, bobPool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -221,6 +221,10 @@ type ChainArbitratorConfig struct {
|
||||
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[lnwallet.AuxSigner]
|
||||
}
|
||||
|
||||
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
||||
@ -307,6 +311,9 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
|
||||
a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||
})
|
||||
a.c.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
|
||||
})
|
||||
|
||||
chanMachine, err := lnwallet.NewLightningChannel(
|
||||
a.c.cfg.Signer, channel, nil, chanOpts...,
|
||||
@ -357,6 +364,9 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
|
||||
a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||
})
|
||||
a.c.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
|
||||
})
|
||||
|
||||
// Finally, we'll force close the channel completing
|
||||
// the force close workflow.
|
||||
|
@ -554,6 +554,10 @@ type Config struct {
|
||||
// able to automatically handle new custom protocol messages related to
|
||||
// the funding process.
|
||||
AuxFundingController fn.Option[AuxFundingController]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[lnwallet.AuxSigner]
|
||||
}
|
||||
|
||||
// Manager acts as an orchestrator/bridge between the wallet's
|
||||
@ -1083,6 +1087,9 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel,
|
||||
f.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||
})
|
||||
f.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
|
||||
})
|
||||
|
||||
// We create the state-machine object which wraps the database state.
|
||||
lnChannel, err := lnwallet.NewLightningChannel(
|
||||
|
@ -567,6 +567,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
AuxLeafStore: fn.Some[lnwallet.AuxLeafStore](
|
||||
&lnwallet.MockAuxLeafStore{},
|
||||
),
|
||||
AuxSigner: fn.Some[lnwallet.AuxSigner](
|
||||
&lnwallet.MockAuxSigner{},
|
||||
),
|
||||
}
|
||||
|
||||
for _, op := range options {
|
||||
@ -677,6 +680,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
||||
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
|
||||
AliasManager: oldCfg.AliasManager,
|
||||
AuxLeafStore: oldCfg.AuxLeafStore,
|
||||
AuxSigner: oldCfg.AuxSigner,
|
||||
})
|
||||
require.NoError(t, err, "failed recreating aliceFundingManager")
|
||||
|
||||
|
@ -2191,10 +2191,20 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
||||
// We just received a new updates to our local commitment
|
||||
// chain, validate this new commitment, closing the link if
|
||||
// invalid.
|
||||
auxSigBlob, err := msg.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
l.failf(
|
||||
LinkFailureError{code: ErrInvalidCommitment},
|
||||
"unable to serialize custom records: %v", err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{
|
||||
CommitSig: msg.CommitSig,
|
||||
HtlcSigs: msg.HtlcSigs,
|
||||
PartialSig: msg.PartialSig,
|
||||
AuxSigBlob: auxSigBlob,
|
||||
})
|
||||
if err != nil {
|
||||
// If we were unable to reconstruct their proposed
|
||||
@ -2621,11 +2631,17 @@ func (l *channelLink) updateCommitTx() error {
|
||||
default:
|
||||
}
|
||||
|
||||
auxBlobRecords, err := lnwire.ParseCustomRecords(newCommit.AuxSigBlob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing aux sigs: %w", err)
|
||||
}
|
||||
|
||||
commitSig := &lnwire.CommitSig{
|
||||
ChanID: l.ChanID(),
|
||||
CommitSig: newCommit.CommitSig,
|
||||
HtlcSigs: newCommit.HtlcSigs,
|
||||
PartialSig: newCommit.PartialSig,
|
||||
CustomRecords: auxBlobRecords,
|
||||
}
|
||||
l.cfg.Peer.SendMessage(false, commitSig)
|
||||
|
||||
|
@ -268,9 +268,12 @@ func TestChannelLinkRevThenSig(t *testing.T) {
|
||||
|
||||
// Restart Bob as well by calling NewLightningChannel.
|
||||
bobSigner := harness.bobChannel.Signer
|
||||
signerMock := lnwallet.NewDefaultAuxSignerMock(t)
|
||||
bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner)
|
||||
bobChannel, err := lnwallet.NewLightningChannel(
|
||||
bobSigner, harness.bobChannel.State(), bobPool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
err = bobPool.Start()
|
||||
@ -403,9 +406,12 @@ func TestChannelLinkSigThenRev(t *testing.T) {
|
||||
|
||||
// Restart Bob as well by calling NewLightningChannel.
|
||||
bobSigner := harness.bobChannel.Signer
|
||||
signerMock := lnwallet.NewDefaultAuxSignerMock(t)
|
||||
bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner)
|
||||
bobChannel, err := lnwallet.NewLightningChannel(
|
||||
bobSigner, harness.bobChannel.State(), bobPool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
err = bobPool.Start()
|
||||
|
@ -351,8 +351,11 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte,
|
||||
)
|
||||
|
||||
alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner)
|
||||
signerMock := lnwallet.NewDefaultAuxSignerMock(t)
|
||||
channelAlice, err := lnwallet.NewLightningChannel(
|
||||
aliceSigner, aliceChannelState, alicePool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -362,6 +365,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte,
|
||||
bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner)
|
||||
channelBob, err := lnwallet.NewLightningChannel(
|
||||
bobSigner, bobChannelState, bobPool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -423,6 +428,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte,
|
||||
|
||||
newAliceChannel, err := lnwallet.NewLightningChannel(
|
||||
aliceSigner, aliceStoredChannel, alicePool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("unable to create new channel: %v",
|
||||
@ -469,6 +476,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte,
|
||||
|
||||
newBobChannel, err := lnwallet.NewLightningChannel(
|
||||
bobSigner, bobStoredChannel, bobPool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(signerMock),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("unable to create new channel: %v",
|
||||
|
@ -9,6 +9,10 @@ import (
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// htlcCustomSigType is the TLV type that is used to encode the custom HTLC
|
||||
// signatures within the custom data for an existing HTLC.
|
||||
var htlcCustomSigType tlv.TlvType65543
|
||||
|
||||
// AuxHtlcDescriptor is a struct that contains the information needed to sign or
|
||||
// verify an HTLC for custom channels.
|
||||
type AuxHtlcDescriptor struct {
|
||||
|
@ -3089,7 +3089,8 @@ func processFeeUpdate(feeUpdate *paymentDescriptor, nextHeight uint64,
|
||||
func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
chanState *channeldb.OpenChannel, leaseExpiry uint32,
|
||||
remoteCommitView *commitment,
|
||||
leafStore fn.Option[AuxLeafStore]) ([]SignJob, chan struct{}, error) {
|
||||
leafStore fn.Option[AuxLeafStore]) ([]SignJob, []AuxSigJob,
|
||||
chan struct{}, error) {
|
||||
|
||||
var (
|
||||
isRemoteInitiator = !chanState.IsInitiator
|
||||
@ -3109,6 +3110,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
numSigs := len(remoteCommitView.incomingHTLCs) +
|
||||
len(remoteCommitView.outgoingHTLCs)
|
||||
sigBatch := make([]SignJob, 0, numSigs)
|
||||
auxSigBatch := make([]AuxSigJob, 0, numSigs)
|
||||
|
||||
var err error
|
||||
cancelChan := make(chan struct{})
|
||||
@ -3123,8 +3125,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w",
|
||||
err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to fetch aux leaves: "+
|
||||
"%w", err)
|
||||
}
|
||||
|
||||
// For each outgoing and incoming HTLC, if the HTLC isn't considered a
|
||||
@ -3173,12 +3175,9 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): hook up signer interface here (later commit
|
||||
// in this PR).
|
||||
|
||||
// Construct a full hash cache as we may be signing a segwit v1
|
||||
// sighash.
|
||||
txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex]
|
||||
@ -3210,6 +3209,11 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
}
|
||||
|
||||
sigBatch = append(sigBatch, sigJob)
|
||||
|
||||
auxSigBatch = append(auxSigBatch, NewAuxSigJob(
|
||||
sigJob, *keyRing, true, newAuxHtlcDescriptor(&htlc),
|
||||
remoteCommitView.customBlob, auxLeaf, cancelChan,
|
||||
))
|
||||
}
|
||||
for _, htlc := range remoteCommitView.outgoingHTLCs {
|
||||
if HtlcIsDust(
|
||||
@ -3253,7 +3257,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
auxLeaf,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Construct a full hash cache as we may be signing a segwit v1
|
||||
@ -3282,13 +3286,19 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// If this is a taproot channel, then we'll need to set the
|
||||
// method type to ensure we generate a valid signature.
|
||||
if chanType.IsTaproot() {
|
||||
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod //nolint:lll
|
||||
//nolint:lll
|
||||
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod
|
||||
}
|
||||
|
||||
sigBatch = append(sigBatch, sigJob)
|
||||
|
||||
auxSigBatch = append(auxSigBatch, NewAuxSigJob(
|
||||
sigJob, *keyRing, false, newAuxHtlcDescriptor(&htlc),
|
||||
remoteCommitView.customBlob, auxLeaf, cancelChan,
|
||||
))
|
||||
}
|
||||
|
||||
return sigBatch, cancelChan, nil
|
||||
return sigBatch, auxSigBatch, cancelChan, nil
|
||||
}
|
||||
|
||||
// createCommitDiff will create a commit diff given a new pending commitment
|
||||
@ -3297,7 +3307,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// new commitment to the remote party. The commit diff returned contains all
|
||||
// information necessary for retransmission.
|
||||
func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
||||
commitSig lnwire.Sig, htlcSigs []lnwire.Sig) *channeldb.CommitDiff {
|
||||
commitSig lnwire.Sig, htlcSigs []lnwire.Sig,
|
||||
auxSigs []fn.Option[tlv.Blob]) (*channeldb.CommitDiff, error) {
|
||||
|
||||
var (
|
||||
logUpdates []channeldb.LogUpdate
|
||||
@ -3366,21 +3377,71 @@ func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
||||
// disk.
|
||||
diskCommit := newCommit.toDiskCommit(lntypes.Remote)
|
||||
|
||||
return &channeldb.CommitDiff{
|
||||
Commitment: *diskCommit,
|
||||
CommitSig: &lnwire.CommitSig{
|
||||
// We prepare the commit sig message to be sent to the remote party.
|
||||
commitSigMsg := &lnwire.CommitSig{
|
||||
ChanID: lnwire.NewChanIDFromOutPoint(
|
||||
lc.channelState.FundingOutpoint,
|
||||
),
|
||||
CommitSig: commitSig,
|
||||
HtlcSigs: htlcSigs,
|
||||
}
|
||||
|
||||
// Encode and check the size of the custom records now.
|
||||
auxCustomRecords, err := fn.MapOptionZ(
|
||||
lc.auxSigner,
|
||||
func(s AuxSigner) fn.Result[lnwire.CustomRecords] {
|
||||
blobOption, err := s.PackSigs(auxSigs).Unpack()
|
||||
if err != nil {
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
// We now serialize the commit sig message without the
|
||||
// custom records to make sure we have space for them.
|
||||
var buf bytes.Buffer
|
||||
err = commitSigMsg.Encode(&buf, 0)
|
||||
if err != nil {
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
// The number of available bytes is the max message size
|
||||
// minus the size of the message without the custom
|
||||
// records. We also subtract 8 bytes for encoding
|
||||
// overhead of the custom records (just some safety
|
||||
// padding).
|
||||
available := lnwire.MaxMsgBody - buf.Len() - 8
|
||||
|
||||
blob := blobOption.UnwrapOr(nil)
|
||||
if len(blob) > available {
|
||||
err = fmt.Errorf("aux sigs size %d exceeds "+
|
||||
"max allowed size of %d", len(blob),
|
||||
available)
|
||||
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
records, err := lnwire.ParseCustomRecords(blob)
|
||||
if err != nil {
|
||||
return fn.Err[lnwire.CustomRecords](err)
|
||||
}
|
||||
|
||||
return fn.Ok(records)
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error packing aux sigs: %w", err)
|
||||
}
|
||||
|
||||
commitSigMsg.CustomRecords = auxCustomRecords
|
||||
|
||||
return &channeldb.CommitDiff{
|
||||
Commitment: *diskCommit,
|
||||
CommitSig: commitSigMsg,
|
||||
LogUpdates: logUpdates,
|
||||
OpenedCircuitKeys: openCircuitKeys,
|
||||
ClosedCircuitKeys: closedCircuitKeys,
|
||||
AddAcks: ackAddRefs,
|
||||
SettleFailAcks: settleFailRefs,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getUnsignedAckedUpdates returns all remote log updates that we haven't
|
||||
@ -3773,6 +3834,10 @@ type CommitSigs struct {
|
||||
// PartialSig is the musig2 partial signature for taproot commitment
|
||||
// transactions.
|
||||
PartialSig lnwire.OptPartialSigWithNonceTLV
|
||||
|
||||
// AuxSigBlob is the blob containing all the auxiliary signatures for
|
||||
// this new commitment state.
|
||||
AuxSigBlob tlv.Blob
|
||||
}
|
||||
|
||||
// NewCommitState wraps the various signatures needed to properly
|
||||
@ -3797,6 +3862,8 @@ type NewCommitState struct {
|
||||
// any). The HTLC signatures are sorted according to the BIP 69 order of the
|
||||
// HTLC's on the commitment transaction. Finally, the new set of pending HTLCs
|
||||
// for the remote party's commitment are also returned.
|
||||
//
|
||||
//nolint:funlen
|
||||
func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
@ -3889,7 +3956,7 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
if lc.channelState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = lc.channelState.ThawHeight
|
||||
}
|
||||
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(
|
||||
sigBatch, auxSigBatch, cancelChan, err := genRemoteHtlcSigJobs(
|
||||
keyRing, lc.channelState, leaseExpiry, newCommitView,
|
||||
lc.leafStore,
|
||||
)
|
||||
@ -3898,6 +3965,17 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
}
|
||||
lc.sigPool.SubmitSignBatch(sigBatch)
|
||||
|
||||
err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error {
|
||||
return a.SubmitSecondLevelSigBatch(
|
||||
NewAuxChanState(lc.channelState), newCommitView.txn,
|
||||
auxSigBatch,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error submitting second level sig "+
|
||||
"batch: %w", err)
|
||||
}
|
||||
|
||||
// While the jobs are being carried out, we'll Sign their version of
|
||||
// the new commitment transaction while we're waiting for the rest of
|
||||
// the HTLC signatures to be processed.
|
||||
@ -3941,11 +4019,16 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
sort.Slice(sigBatch, func(i, j int) bool {
|
||||
return sigBatch[i].OutputIndex < sigBatch[j].OutputIndex
|
||||
})
|
||||
sort.Slice(auxSigBatch, func(i, j int) bool {
|
||||
return auxSigBatch[i].OutputIndex < auxSigBatch[j].OutputIndex
|
||||
})
|
||||
|
||||
// With the jobs sorted, we'll now iterate through all the responses to
|
||||
// gather each of the signatures in order.
|
||||
htlcSigs = make([]lnwire.Sig, 0, len(sigBatch))
|
||||
for _, htlcSigJob := range sigBatch {
|
||||
auxSigs := make([]fn.Option[tlv.Blob], 0, len(auxSigBatch))
|
||||
for i := range sigBatch {
|
||||
htlcSigJob := sigBatch[i]
|
||||
jobResp := <-htlcSigJob.Resp
|
||||
|
||||
// If an error occurred, then we'll cancel any other active
|
||||
@ -3956,12 +4039,34 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
}
|
||||
|
||||
htlcSigs = append(htlcSigs, jobResp.Sig)
|
||||
|
||||
if lc.auxSigner.IsNone() {
|
||||
continue
|
||||
}
|
||||
|
||||
auxHtlcSigJob := auxSigBatch[i]
|
||||
auxJobResp := <-auxHtlcSigJob.Resp
|
||||
|
||||
// If an error occurred, then we'll cancel any other active
|
||||
// jobs.
|
||||
if auxJobResp.Err != nil {
|
||||
close(cancelChan)
|
||||
return nil, auxJobResp.Err
|
||||
}
|
||||
|
||||
auxSigs = append(auxSigs, auxJobResp.SigBlob)
|
||||
}
|
||||
|
||||
// As we're about to proposer a new commitment state for the remote
|
||||
// party, we'll write this pending state to disk before we exit, so we
|
||||
// can retransmit it if necessary.
|
||||
commitDiff := lc.createCommitDiff(newCommitView, sig, htlcSigs)
|
||||
commitDiff, err := lc.createCommitDiff(
|
||||
newCommitView, sig, htlcSigs, auxSigs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = lc.channelState.AppendRemoteCommitChain(commitDiff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -3975,11 +4080,18 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
// latest commitment update.
|
||||
lc.commitChains.Remote.addCommitment(newCommitView)
|
||||
|
||||
auxSigBlob, err := commitDiff.CommitSig.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to serialize aux sig blob: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
return &NewCommitState{
|
||||
CommitSigs: &CommitSigs{
|
||||
CommitSig: sig,
|
||||
HtlcSigs: htlcSigs,
|
||||
PartialSig: lnwire.MaybePartialSigWithNonce(partialSig),
|
||||
AuxSigBlob: auxSigBlob,
|
||||
},
|
||||
PendingHTLCs: commitDiff.Commitment.Htlcs,
|
||||
}, nil
|
||||
@ -3991,8 +4103,8 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
||||
// each time. After we receive the channel reestablish message, we learn the
|
||||
// nonce we need to use for the remote party. As a result, we need to generate
|
||||
// the partial signature again with the new nonce.
|
||||
func (lc *LightningChannel) resignMusigCommit(commitTx *wire.MsgTx,
|
||||
) (lnwire.OptPartialSigWithNonceTLV, error) {
|
||||
func (lc *LightningChannel) resignMusigCommit(
|
||||
commitTx *wire.MsgTx) (lnwire.OptPartialSigWithNonceTLV, error) {
|
||||
|
||||
remoteSession := lc.musigSessions.RemoteSession
|
||||
musig, err := remoteSession.SignCommit(commitTx)
|
||||
@ -4197,6 +4309,15 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
||||
// If we signed this state, then we'll accumulate
|
||||
// another update to send over.
|
||||
case err == nil:
|
||||
customRecords, err := lnwire.ParseCustomRecords(
|
||||
newCommit.AuxSigBlob,
|
||||
)
|
||||
if err != nil {
|
||||
sErr := fmt.Errorf("error parsing aux "+
|
||||
"sigs: %w", err)
|
||||
return nil, nil, nil, sErr
|
||||
}
|
||||
|
||||
commitSig := &lnwire.CommitSig{
|
||||
ChanID: lnwire.NewChanIDFromOutPoint(
|
||||
lc.channelState.FundingOutpoint,
|
||||
@ -4204,6 +4325,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
||||
CommitSig: newCommit.CommitSig,
|
||||
HtlcSigs: newCommit.HtlcSigs,
|
||||
PartialSig: newCommit.PartialSig,
|
||||
CustomRecords: customRecords,
|
||||
}
|
||||
|
||||
updates = append(updates, commitSig)
|
||||
@ -4496,7 +4618,8 @@ func (lc *LightningChannel) computeView(view *HtlcView,
|
||||
func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
localCommitmentView *commitment, keyRing *CommitmentKeyRing,
|
||||
htlcSigs []lnwire.Sig, leaseExpiry uint32,
|
||||
leafStore fn.Option[AuxLeafStore]) ([]VerifyJob, error) {
|
||||
leafStore fn.Option[AuxLeafStore], auxSigner fn.Option[AuxSigner],
|
||||
sigBlob fn.Option[tlv.Blob]) ([]VerifyJob, []AuxVerifyJob, error) {
|
||||
|
||||
var (
|
||||
isLocalInitiator = chanState.IsInitiator
|
||||
@ -4515,6 +4638,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
numHtlcs := len(localCommitmentView.incomingHTLCs) +
|
||||
len(localCommitmentView.outgoingHTLCs)
|
||||
verifyJobs := make([]VerifyJob, 0, numHtlcs)
|
||||
auxVerifyJobs := make([]AuxVerifyJob, 0, numHtlcs)
|
||||
|
||||
diskCommit := localCommitmentView.toDiskCommit(lntypes.Local)
|
||||
auxResult, err := fn.MapOptionZ(
|
||||
@ -4526,7 +4650,20 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
|
||||
return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
// If we have a sig blob, then we'll attempt to map that to individual
|
||||
// blobs for each HTLC we might need a signature for.
|
||||
auxHtlcSigs, err := fn.MapOptionZ(
|
||||
auxSigner, func(a AuxSigner) fn.Result[[]fn.Option[tlv.Blob]] {
|
||||
return a.UnpackSigs(sigBlob)
|
||||
},
|
||||
).Unpack()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error unpacking aux sigs: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
// We'll iterate through each output in the commitment transaction,
|
||||
@ -4539,6 +4676,9 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
htlcIndex uint64
|
||||
sigHash func() ([]byte, error)
|
||||
sig input.Signature
|
||||
htlc *paymentDescriptor
|
||||
incoming bool
|
||||
auxLeaf input.AuxTapLeaf
|
||||
err error
|
||||
)
|
||||
|
||||
@ -4548,10 +4688,12 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
// If this output index is found within the incoming HTLC
|
||||
// index, then this means that we need to generate an HTLC
|
||||
// success transaction in order to validate the signature.
|
||||
//nolint:lll
|
||||
case localCommitmentView.incomingHTLCIndex[outputIndex] != nil:
|
||||
htlc := localCommitmentView.incomingHTLCIndex[outputIndex]
|
||||
htlc = localCommitmentView.incomingHTLCIndex[outputIndex]
|
||||
|
||||
htlcIndex = htlc.HtlcIndex
|
||||
incoming = true
|
||||
|
||||
sigHash = func() ([]byte, error) {
|
||||
op := wire.OutPoint{
|
||||
@ -4617,7 +4759,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
|
||||
// Make sure there are more signatures left.
|
||||
if i >= len(htlcSigs) {
|
||||
return nil, fmt.Errorf("not enough HTLC " +
|
||||
return nil, nil, fmt.Errorf("not enough HTLC " +
|
||||
"signatures")
|
||||
}
|
||||
|
||||
@ -4633,15 +4775,16 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
// is valid.
|
||||
sig, err = htlcSigs[i].ToSignature()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
htlc.sig = sig
|
||||
|
||||
// Otherwise, if this is an outgoing HTLC, then we'll need to
|
||||
// generate a timeout transaction so we can verify the
|
||||
// signature presented.
|
||||
//nolint:lll
|
||||
case localCommitmentView.outgoingHTLCIndex[outputIndex] != nil:
|
||||
htlc := localCommitmentView.outgoingHTLCIndex[outputIndex]
|
||||
htlc = localCommitmentView.outgoingHTLCIndex[outputIndex]
|
||||
|
||||
htlcIndex = htlc.HtlcIndex
|
||||
|
||||
@ -4712,7 +4855,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
|
||||
// Make sure there are more signatures left.
|
||||
if i >= len(htlcSigs) {
|
||||
return nil, fmt.Errorf("not enough HTLC " +
|
||||
return nil, nil, fmt.Errorf("not enough HTLC " +
|
||||
"signatures")
|
||||
}
|
||||
|
||||
@ -4728,7 +4871,7 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
// is valid.
|
||||
sig, err = htlcSigs[i].ToSignature()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
htlc.sig = sig
|
||||
@ -4744,17 +4887,40 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
||||
SigHash: sigHash,
|
||||
})
|
||||
|
||||
if len(auxHtlcSigs) > i {
|
||||
auxSig := auxHtlcSigs[i]
|
||||
auxVerifyJob := NewAuxVerifyJob(
|
||||
auxSig, *keyRing, incoming,
|
||||
newAuxHtlcDescriptor(htlc),
|
||||
localCommitmentView.customBlob, auxLeaf,
|
||||
)
|
||||
|
||||
if htlc.CustomRecords == nil {
|
||||
htlc.CustomRecords = make(lnwire.CustomRecords)
|
||||
}
|
||||
|
||||
// As this HTLC has a custom signature associated with
|
||||
// it, store it in the custom records map so we can
|
||||
// write to disk later.
|
||||
sigType := htlcCustomSigType.TypeVal()
|
||||
htlc.CustomRecords[uint64(sigType)] = auxSig.UnwrapOr(
|
||||
nil,
|
||||
)
|
||||
|
||||
auxVerifyJobs = append(auxVerifyJobs, auxVerifyJob)
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// If we received a number of HTLC signatures that doesn't match our
|
||||
// commitment, we'll return an error now.
|
||||
if len(htlcSigs) != i {
|
||||
return nil, fmt.Errorf("number of htlc sig mismatch. "+
|
||||
return nil, nil, fmt.Errorf("number of htlc sig mismatch. "+
|
||||
"Expected %v sigs, got %v", i, len(htlcSigs))
|
||||
}
|
||||
|
||||
return verifyJobs, nil
|
||||
return verifyJobs, auxVerifyJobs, nil
|
||||
}
|
||||
|
||||
// InvalidCommitSigError is a struct that implements the error interface to
|
||||
@ -4913,6 +5079,11 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
||||
localCommitmentView.ourBalance, localCommitmentView.theirBalance,
|
||||
lnutils.SpewLogClosure(localCommitmentView.txn))
|
||||
|
||||
var auxSigBlob fn.Option[tlv.Blob]
|
||||
if commitSigs.AuxSigBlob != nil {
|
||||
auxSigBlob = fn.Some(commitSigs.AuxSigBlob)
|
||||
}
|
||||
|
||||
// As an optimization, we'll generate a series of jobs for the worker
|
||||
// pool to verify each of the HTLC signatures presented. Once
|
||||
// generated, we'll submit these jobs to the worker pool.
|
||||
@ -4920,9 +5091,10 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
||||
if lc.channelState.ChanType.HasLeaseExpiration() {
|
||||
leaseExpiry = lc.channelState.ThawHeight
|
||||
}
|
||||
verifyJobs, err := genHtlcSigValidationJobs(
|
||||
verifyJobs, auxVerifyJobs, err := genHtlcSigValidationJobs(
|
||||
lc.channelState, localCommitmentView, keyRing,
|
||||
commitSigs.HtlcSigs, leaseExpiry, lc.leafStore,
|
||||
commitSigs.HtlcSigs, leaseExpiry, lc.leafStore, lc.auxSigner,
|
||||
auxSigBlob,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -5075,6 +5247,18 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know all the normal sigs are valid, we'll also verify
|
||||
// the aux jobs, if any exist.
|
||||
err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error {
|
||||
return a.VerifySecondLevelSigs(
|
||||
NewAuxChanState(lc.channelState), localCommitTx,
|
||||
auxVerifyJobs,
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to validate aux sigs: %w", err)
|
||||
}
|
||||
|
||||
// The signature checks out, so we can now add the new commitment to
|
||||
// our local commitment chain. For regular channels, we can just
|
||||
// serialize the ECDSA sig. For taproot channels, we'll serialize the
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -702,6 +703,68 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) {
|
||||
require.NoError(t, err, "unable to receive bob's commitment")
|
||||
}
|
||||
|
||||
// TestCommitHTLCSigCustomRecordSize asserts that custom records produced for
|
||||
// a commitment_signed message are properly limited in size.
|
||||
func TestCommitHTLCSigCustomRecordSize(t *testing.T) {
|
||||
aliceChannel, bobChannel, err := CreateTestChannels(
|
||||
t, channeldb.SimpleTaprootFeatureBit|
|
||||
channeldb.TapscriptRootBit,
|
||||
)
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
const (
|
||||
htlcAmt = lnwire.MilliSatoshi(20000000)
|
||||
numHtlcs = 2
|
||||
)
|
||||
|
||||
largeRecords := lnwire.CustomRecords{
|
||||
lnwire.MinCustomRecordsTlvType: bytes.Repeat([]byte{0}, 65_500),
|
||||
}
|
||||
largeBlob, err := largeRecords.Serialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
aliceChannel.auxSigner.WhenSome(func(a AuxSigner) {
|
||||
mockSigner, ok := a.(*MockAuxSigner)
|
||||
require.True(t, ok, "expected MockAuxSigner")
|
||||
|
||||
// Replace the default PackSigs implementation to return a
|
||||
// large custom records blob.
|
||||
mockSigner.ExpectedCalls = fn.Filter(func(c *mock.Call) bool {
|
||||
return c.Method != "PackSigs"
|
||||
}, mockSigner.ExpectedCalls)
|
||||
mockSigner.On("PackSigs", mock.Anything).
|
||||
Return(fn.Ok(fn.Some(largeBlob)))
|
||||
})
|
||||
|
||||
// Add HTLCs with identical payment hashes and amounts, but descending
|
||||
// CLTV values. We will expect the signatures to appear in the reverse
|
||||
// order that the HTLCs are added due to the commitment sorting.
|
||||
for i := 0; i < numHtlcs; i++ {
|
||||
var (
|
||||
preimage lntypes.Preimage
|
||||
hash = preimage.Hash()
|
||||
)
|
||||
|
||||
htlc := &lnwire.UpdateAddHTLC{
|
||||
ID: uint64(i),
|
||||
PaymentHash: hash,
|
||||
Amount: htlcAmt,
|
||||
Expiry: uint32(numHtlcs - i),
|
||||
}
|
||||
|
||||
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
||||
t.Fatalf("alice unable to add htlc: %v", err)
|
||||
}
|
||||
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||
t.Fatalf("bob unable to receive htlc: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We expect an error because of the large custom records blob.
|
||||
_, err = aliceChannel.SignNextCommitment()
|
||||
require.ErrorContains(t, err, "exceeds max allowed size")
|
||||
}
|
||||
|
||||
// TestCooperativeChannelClosure checks that the coop close process finishes
|
||||
// with an agreement from both parties, and that the final balances of the
|
||||
// close tx check out.
|
||||
@ -3046,6 +3109,10 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
|
||||
return channelNew, nil
|
||||
}
|
||||
|
||||
// testChanSyncOweCommitment tests that if Bob restarts (and then Alice) before
|
||||
// he receives Alice's CommitSig message, then Alice concludes that she needs
|
||||
// to re-send the CommitDiff. After the diff has been sent, both nodes should
|
||||
// resynchronize and be able to complete the dangling commit.
|
||||
func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
@ -3210,8 +3277,10 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
|
||||
len(commitSigMsg.HtlcSigs))
|
||||
}
|
||||
for i, htlcSig := range commitSigMsg.HtlcSigs {
|
||||
if !bytes.Equal(htlcSig.RawBytes(),
|
||||
aliceNewCommit.HtlcSigs[i].RawBytes()) {
|
||||
if !bytes.Equal(
|
||||
htlcSig.RawBytes(),
|
||||
aliceNewCommit.HtlcSigs[i].RawBytes(),
|
||||
) {
|
||||
|
||||
t.Fatalf("htlc sig msgs don't match: "+
|
||||
"expected %v got %v",
|
||||
@ -3389,6 +3458,100 @@ func TestChanSyncOweCommitment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testSigBlob struct {
|
||||
BlobInt tlv.RecordT[tlv.TlvType65634, uint16]
|
||||
}
|
||||
|
||||
// TestChanSyncOweCommitmentAuxSigner tests that when one party owes a
|
||||
// signature after a channel reest, if an aux signer is present, then the
|
||||
// signature message sent includes the additional aux sigs as extra data.
|
||||
func TestChanSyncOweCommitmentAuxSigner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
chanType := channeldb.SingleFunderTweaklessBit |
|
||||
channeldb.AnchorOutputsBit | channeldb.SimpleTaprootFeatureBit |
|
||||
channeldb.TapscriptRootBit
|
||||
|
||||
aliceChannel, bobChannel, err := CreateTestChannels(t, chanType)
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
// We'll now manually attach an aux signer to Alice's channel.
|
||||
auxSigner := &MockAuxSigner{}
|
||||
aliceChannel.auxSigner = fn.Some[AuxSigner](auxSigner)
|
||||
|
||||
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
||||
copy(
|
||||
fakeOnionBlob[:],
|
||||
bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize),
|
||||
)
|
||||
|
||||
// To kick things off, we'll have Alice send a single HTLC to Bob.
|
||||
htlcAmt := lnwire.NewMSatFromSatoshis(20000)
|
||||
var bobPreimage [32]byte
|
||||
copy(bobPreimage[:], bytes.Repeat([]byte{0}, 32))
|
||||
rHash := sha256.Sum256(bobPreimage[:])
|
||||
h := &lnwire.UpdateAddHTLC{
|
||||
PaymentHash: rHash,
|
||||
Amount: htlcAmt,
|
||||
Expiry: uint32(10),
|
||||
OnionBlob: fakeOnionBlob,
|
||||
}
|
||||
|
||||
_, err = aliceChannel.AddHTLC(h, nil)
|
||||
require.NoError(t, err, "unable to recv bob's htlc: %v", err)
|
||||
|
||||
// We'll set up the mock to expect calls to PackSigs and also
|
||||
// SubmitSubmitSecondLevelSigBatch.
|
||||
var sigBlobBuf bytes.Buffer
|
||||
sigBlob := testSigBlob{
|
||||
BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5),
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record())
|
||||
require.NoError(t, err, "unable to create tlv stream")
|
||||
require.NoError(t, tlvStream.Encode(&sigBlobBuf))
|
||||
|
||||
auxSigner.On(
|
||||
"SubmitSecondLevelSigBatch", mock.Anything, mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(nil).Twice()
|
||||
auxSigner.On(
|
||||
"PackSigs", mock.Anything,
|
||||
).Return(
|
||||
fn.Ok(fn.Some(sigBlobBuf.Bytes())), nil,
|
||||
)
|
||||
|
||||
_, err = aliceChannel.SignNextCommitment()
|
||||
require.NoError(t, err, "unable to sign commitment")
|
||||
|
||||
_, err = aliceChannel.GenMusigNonces()
|
||||
require.NoError(t, err, "unable to generate musig nonces")
|
||||
|
||||
// Next we'll simulate a restart, by having Bob send over a chan sync
|
||||
// message to Alice.
|
||||
bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg()
|
||||
require.NoError(t, err, "unable to produce chan sync msg")
|
||||
|
||||
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(
|
||||
bobSyncMsg,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, aliceMsgsToSend, 2)
|
||||
|
||||
// The first message should be an update add HTLC.
|
||||
require.IsType(t, &lnwire.UpdateAddHTLC{}, aliceMsgsToSend[0])
|
||||
|
||||
// The second should be a commit sig message.
|
||||
sigMsg, ok := aliceMsgsToSend[1].(*lnwire.CommitSig)
|
||||
require.True(t, ok)
|
||||
require.True(t, sigMsg.PartialSig.IsSome())
|
||||
|
||||
// The signature should have the CustomRecords field set.
|
||||
require.NotEmpty(t, sigMsg.CustomRecords)
|
||||
}
|
||||
|
||||
func testChanSyncOweCommitmentPendingRemote(t *testing.T,
|
||||
chanType channeldb.ChannelType) {
|
||||
|
||||
@ -3398,7 +3561,10 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T,
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
var fakeOnionBlob [lnwire.OnionPacketSize]byte
|
||||
copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize))
|
||||
copy(
|
||||
fakeOnionBlob[:],
|
||||
bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize),
|
||||
)
|
||||
|
||||
// We'll start off the scenario where Bob send two htlcs to Alice in a
|
||||
// single state update.
|
||||
@ -3437,7 +3603,9 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T,
|
||||
|
||||
// Next, Alice settles the HTLCs from Bob in distinct state updates.
|
||||
for i := 0; i < numHtlcs; i++ {
|
||||
err = aliceChannel.SettleHTLC(preimages[i], uint64(i), nil, nil, nil)
|
||||
err = aliceChannel.SettleHTLC(
|
||||
preimages[i], uint64(i), nil, nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle htlc: %v", err)
|
||||
}
|
||||
@ -3727,7 +3895,7 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) {
|
||||
}
|
||||
|
||||
// TestChanSyncOweRevocation tests that if Bob restarts (and then Alice) before
|
||||
// he receiver's Alice's RevokeAndAck message, then Alice concludes that she
|
||||
// he received Alice's RevokeAndAck message, then Alice concludes that she
|
||||
// needs to re-send the RevokeAndAck. After the revocation has been sent, both
|
||||
// nodes should be able to successfully complete another state transition.
|
||||
func TestChanSyncOweRevocation(t *testing.T) {
|
||||
|
@ -67,4 +67,8 @@ type Config struct {
|
||||
// AuxLeafStore is an optional store that can be used to store auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxLeafStore fn.Option[AuxLeafStore]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[AuxSigner]
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -441,3 +442,54 @@ func (*MockAuxLeafStore) ApplyHtlcView(
|
||||
|
||||
return fn.Ok(fn.None[tlv.Blob]())
|
||||
}
|
||||
|
||||
// MockAuxSigner is a mock implementation of the AuxSigner interface.
|
||||
type MockAuxSigner struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and
|
||||
// processes them asynchronously.
|
||||
func (a *MockAuxSigner) SubmitSecondLevelSigBatch(chanState AuxChanState,
|
||||
tx *wire.MsgTx, jobs []AuxSigJob) error {
|
||||
|
||||
args := a.Called(chanState, tx, jobs)
|
||||
|
||||
// While we return, we'll also send back an instant response for the
|
||||
// set of jobs.
|
||||
for _, sigJob := range jobs {
|
||||
sigJob.Resp <- AuxSigJobResp{}
|
||||
}
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// PackSigs takes a series of aux signatures and packs them into a
|
||||
// single blob that can be sent alongside the CommitSig messages.
|
||||
func (a *MockAuxSigner) PackSigs(
|
||||
sigs []fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]] {
|
||||
|
||||
args := a.Called(sigs)
|
||||
|
||||
return args.Get(0).(fn.Result[fn.Option[tlv.Blob]])
|
||||
}
|
||||
|
||||
// UnpackSigs takes a packed blob of signatures and returns the
|
||||
// original signatures for each HTLC, keyed by HTLC index.
|
||||
func (a *MockAuxSigner) UnpackSigs(
|
||||
sigs fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]] {
|
||||
|
||||
args := a.Called(sigs)
|
||||
|
||||
return args.Get(0).(fn.Result[[]fn.Option[tlv.Blob]])
|
||||
}
|
||||
|
||||
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux
|
||||
// sig jobs.
|
||||
func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState,
|
||||
tx *wire.MsgTx, jobs []AuxVerifyJob) error {
|
||||
|
||||
args := a.Called(chanState, tx, jobs)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@ -21,6 +22,8 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -369,9 +372,13 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
||||
|
||||
// TODO(roasbeef): make mock version of pre-image store
|
||||
|
||||
auxSigner := NewDefaultAuxSignerMock(t)
|
||||
|
||||
alicePool := NewSigPool(1, aliceSigner)
|
||||
channelAlice, err := NewLightningChannel(
|
||||
aliceSigner, aliceChannelState, alicePool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -386,6 +393,8 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
||||
bobPool := NewSigPool(1, bobSigner)
|
||||
channelBob, err := NewLightningChannel(
|
||||
bobSigner, bobChannelState, bobPool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -586,3 +595,38 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDefaultAuxSignerMock(t *testing.T) *MockAuxSigner {
|
||||
auxSigner := &MockAuxSigner{}
|
||||
|
||||
type testSigBlob struct {
|
||||
BlobInt tlv.RecordT[tlv.TlvType65634, uint16]
|
||||
}
|
||||
|
||||
var sigBlobBuf bytes.Buffer
|
||||
sigBlob := testSigBlob{
|
||||
BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5),
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record())
|
||||
require.NoError(t, err, "unable to create tlv stream")
|
||||
require.NoError(t, tlvStream.Encode(&sigBlobBuf))
|
||||
|
||||
auxSigner.On(
|
||||
"SubmitSecondLevelSigBatch", mock.Anything, mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(nil)
|
||||
auxSigner.On(
|
||||
"PackSigs", mock.Anything,
|
||||
).Return(fn.Ok(fn.Some(sigBlobBuf.Bytes())))
|
||||
auxSigner.On(
|
||||
"UnpackSigs", mock.Anything,
|
||||
).Return(fn.Ok([]fn.Option[tlv.Blob]{
|
||||
fn.Some(sigBlobBuf.Bytes()),
|
||||
}))
|
||||
auxSigner.On(
|
||||
"VerifySecondLevelSigs", mock.Anything, mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(nil)
|
||||
|
||||
return auxSigner
|
||||
}
|
||||
|
@ -1018,9 +1018,12 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp
|
||||
tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2,
|
||||
}, nil)
|
||||
|
||||
auxSigner := NewDefaultAuxSignerMock(t)
|
||||
remotePool := NewSigPool(1, remoteSigner)
|
||||
channelRemote, err := NewLightningChannel(
|
||||
remoteSigner, remoteChannelState, remotePool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, remotePool.Start())
|
||||
@ -1028,6 +1031,8 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp
|
||||
localPool := NewSigPool(1, localSigner)
|
||||
channelLocal, err := NewLightningChannel(
|
||||
localSigner, localChannelState, localPool,
|
||||
WithLeafStore(&MockAuxLeafStore{}),
|
||||
WithAuxSigner(auxSigner),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, localPool.Start())
|
||||
|
@ -2607,6 +2607,9 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
||||
l.Cfg.AuxLeafStore.WhenSome(func(s AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, WithLeafStore(s))
|
||||
})
|
||||
l.Cfg.AuxSigner.WhenSome(func(s AuxSigner) {
|
||||
chanOpts = append(chanOpts, WithAuxSigner(s))
|
||||
})
|
||||
|
||||
// First, we'll obtain a fully signed commitment transaction so we can
|
||||
// pass into it on the chanvalidate package for verification.
|
||||
|
@ -376,6 +376,10 @@ type Config struct {
|
||||
// leaves for certain custom channel types.
|
||||
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
|
||||
|
||||
// AuxSigner is an optional signer that can be used to sign auxiliary
|
||||
// leaves for certain custom channel types.
|
||||
AuxSigner fn.Option[lnwallet.AuxSigner]
|
||||
|
||||
// PongBuf is a slice we'll reuse instead of allocating memory on the
|
||||
// heap. Since only reads will occur and no writes, there is no need
|
||||
// for any synchronization primitives. As a result, it's safe to share
|
||||
@ -952,6 +956,9 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
||||
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||
})
|
||||
p.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
|
||||
})
|
||||
lnChan, err := lnwallet.NewLightningChannel(
|
||||
p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts...,
|
||||
)
|
||||
@ -4164,6 +4171,9 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error {
|
||||
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
|
||||
})
|
||||
p.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
|
||||
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
|
||||
})
|
||||
|
||||
// If not already active, we'll add this channel to the set of active
|
||||
// channels, so we can look it up later easily according to its channel
|
||||
|
@ -304,6 +304,8 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a,
|
||||
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
||||
channelAlice, err := lnwallet.NewLightningChannel(
|
||||
aliceSigner, aliceChannelState, alicePool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(&lnwallet.MockAuxSigner{}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -316,6 +318,8 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a,
|
||||
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
||||
channelBob, err := lnwallet.NewLightningChannel(
|
||||
bobSigner, bobChannelState, bobPool,
|
||||
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
||||
lnwallet.WithAuxSigner(&lnwallet.MockAuxSigner{}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1286,6 +1286,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
return &pc.Incoming
|
||||
},
|
||||
AuxLeafStore: implCfg.AuxLeafStore,
|
||||
AuxSigner: implCfg.AuxSigner,
|
||||
}, dbs.ChanStateDB)
|
||||
|
||||
// Select the configuration and funding parameters for Bitcoin.
|
||||
@ -1534,6 +1535,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
AliasManager: s.aliasMgr,
|
||||
IsSweeperOutpoint: s.sweeper.IsSweeperOutpoint,
|
||||
AuxFundingController: implCfg.AuxFundingController,
|
||||
AuxSigner: implCfg.AuxSigner,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -4089,6 +4091,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
||||
MaxFeeExposure: thresholdMSats,
|
||||
Quit: s.quit,
|
||||
AuxLeafStore: s.implCfg.AuxLeafStore,
|
||||
AuxSigner: s.implCfg.AuxSigner,
|
||||
MsgRouter: s.implCfg.MsgRouter,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user