loopdb: refactor the SwapContract to hold all HTLC keys

This commit adds a new struct to hold all HTLC keys and refactors the
SwapContract which is used by both loopin and loopout swaps to use this
new struct. The newly added internal keys will for now hold the script
keys to keep everything equivalent but are already stored and read back
if the protocol version is set to MuSig2.
pull/547/head
Andras Banki-Horvath 1 year ago
parent cc5e26b9c9
commit da4bcbea10
No known key found for this signature in database
GPG Key ID: 80E5375C094198D8

@ -240,8 +240,12 @@ func testLoopOutResume(t *testing.T, confs uint32, expired, preimageRevealed,
Preimage: preimage,
AmountRequested: amt,
CltvExpiry: 744,
ReceiverKey: receiverKey,
SenderKey: senderKey,
HtlcKeys: loopdb.HtlcKeys{
SenderScriptKey: senderKey,
SenderInternalPubKey: senderKey,
ReceiverScriptKey: receiverKey,
ReceiverInternalPubKey: receiverKey,
},
MaxSwapFee: 60000,
MaxMinerFee: 50000,
ProtocolVersion: protocolVersion,

@ -10,6 +10,31 @@ import (
"github.com/lightningnetwork/lnd/lntypes"
)
// HtlcKeys is a holder of all keys used when constructing the swap HTLC. Since
// it's used for both loop in and loop out swaps it may hold partial information
// about the sender or receiver depending on the swap type.
type HtlcKeys struct {
// SenderScriptKey is the sender's public key that is used in the HTLC,
// specifically when constructing the script spend scripts.
SenderScriptKey [33]byte
// SenderInternalPubKey is the sender's internal pubkey that is used in
// taproot HTLCs as part of the aggregate internal key.
SenderInternalPubKey [33]byte
// ReceiverScriptKey is the receiver's public key that is used in the
// HTLC, specifically when constructing the script spend scripts.
ReceiverScriptKey [33]byte
// ReceiverInternalPubKey is the sender's internal pubkey that is used
// in taproot HTLCs as part of the aggregate internal key.
ReceiverInternalPubKey [33]byte
// ClientScriptKeyLocator is the client's key locator for the key used
// in the HTLC script spend scripts.
ClientScriptKeyLocator keychain.KeyLocator
}
// SwapContract contains the base data that is serialized to persistent storage
// for pending swaps.
type SwapContract struct {
@ -19,18 +44,8 @@ type SwapContract struct {
// AmountRequested is the total amount of the swap.
AmountRequested btcutil.Amount
// SenderKey is the key of the sender that will be used in the on-chain
// HTLC.
SenderKey [33]byte
// ReceiverKey is the of the receiver that will be used in the on-chain
// HTLC.
ReceiverKey [33]byte
// ClientKeyLocator is the key locator (family and index) for the client
// key. It is for the receiver key if this is a loop out contract, or
// the sender key if this is a loop in contract.
ClientKeyLocator keychain.KeyLocator
// HtlcKeys holds all keys used in the swap HTLC construction.
HtlcKeys HtlcKeys
// CltvExpiry is the total absolute CLTV expiry of the swap.
CltvExpiry int32

@ -63,7 +63,7 @@ func serializeLoopInContract(swap *LoopInContract) (
return nil, err
}
n, err := b.Write(swap.SenderKey[:])
n, err := b.Write(swap.HtlcKeys.SenderScriptKey[:])
if err != nil {
return nil, err
}
@ -71,7 +71,7 @@ func serializeLoopInContract(swap *LoopInContract) (
return nil, fmt.Errorf("sender key has invalid length")
}
n, err = b.Write(swap.ReceiverKey[:])
n, err = b.Write(swap.HtlcKeys.ReceiverScriptKey[:])
if err != nil {
return nil, err
}
@ -161,7 +161,7 @@ func deserializeLoopInContract(value []byte) (*LoopInContract, error) {
return nil, err
}
n, err := r.Read(contract.SenderKey[:])
n, err := r.Read(contract.HtlcKeys.SenderScriptKey[:])
if err != nil {
return nil, err
}
@ -169,7 +169,7 @@ func deserializeLoopInContract(value []byte) (*LoopInContract, error) {
return nil, fmt.Errorf("sender key has invalid length")
}
n, err = r.Read(contract.ReceiverKey[:])
n, err = r.Read(contract.HtlcKeys.ReceiverScriptKey[:])
if err != nil {
return nil, err
}

@ -134,7 +134,7 @@ func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) (
return nil, err
}
n, err := r.Read(contract.SenderKey[:])
n, err := r.Read(contract.HtlcKeys.SenderScriptKey[:])
if err != nil {
return nil, err
}
@ -142,7 +142,7 @@ func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) (
return nil, fmt.Errorf("sender key has invalid length")
}
n, err = r.Read(contract.ReceiverKey[:])
n, err = r.Read(contract.HtlcKeys.ReceiverScriptKey[:])
if err != nil {
return nil, err
}
@ -229,7 +229,7 @@ func serializeLoopOutContract(swap *LoopOutContract) (
return nil, err
}
n, err := b.Write(swap.SenderKey[:])
n, err := b.Write(swap.HtlcKeys.SenderScriptKey[:])
if err != nil {
return nil, err
}
@ -237,7 +237,7 @@ func serializeLoopOutContract(swap *LoopOutContract) (
return nil, fmt.Errorf("sender key has invalid length")
}
n, err = b.Write(swap.ReceiverKey[:])
n, err = b.Write(swap.HtlcKeys.ReceiverScriptKey[:])
if err != nil {
return nil, err
}

@ -68,7 +68,7 @@ const (
// experimentalRPCProtocolVersion defines the RPC protocol version that
// includes all currently experimentally released features.
experimentalRPCProtocolVersion = looprpc.ProtocolVersion_HTLC_V3
experimentalRPCProtocolVersion = looprpc.ProtocolVersion_MUSIG2
)
var (

@ -112,9 +112,30 @@ var (
// value: concatenation of uint32 values [family, index].
keyLocatorKey = []byte("keylocator")
// senderInternalPubKeyKey is the key that stores the sender's internal
// public key which used when constructing the swap HTLC.
//
// path: loopInBucket/loopOutBucket -> swapBucket[hash]
// -> senderInternalPubKeyKey
// value: serialized public key.
senderInternalPubKeyKey = []byte("sender-internal-pubkey")
// receiverInternalPubKeyKey is the key that stores the receiver's
// internal public key which is used when constructing the swap HTLC.
//
// path: loopInBucket/loopOutBucket -> swapBucket[hash]
// -> receiverInternalPubKeyKey
// value: serialized public key.
receiverInternalPubKeyKey = []byte("receiver-internal-pubkey")
byteOrder = binary.BigEndian
// keyLength is the length of a serialized public key.
keyLength = 33
// errInvalidKey is returned when a serialized key is not the expected
// length.
errInvalidKey = fmt.Errorf("invalid serialized key")
)
const (
@ -233,6 +254,95 @@ func NewBoltSwapStore(dbPath string, chainParams *chaincfg.Params) (
}, nil
}
// marshalHtlcKeys marshals the HTLC keys of the swap contract into the swap
// bucket.
func marshalHtlcKeys(swapBucket *bbolt.Bucket, contract *SwapContract) error {
var err error
// Store the key locator for swaps that use taproot HTLCs.
if contract.ProtocolVersion >= ProtocolVersionHtlcV3 {
keyLocator, err := MarshalKeyLocator(
contract.HtlcKeys.ClientScriptKeyLocator,
)
if err != nil {
return err
}
err = swapBucket.Put(keyLocatorKey, keyLocator)
if err != nil {
return err
}
}
// Store the internal keys for MuSig2 swaps.
if contract.ProtocolVersion >= ProtocolVersionMuSig2 {
// Internal pubkeys are always filled.
err = swapBucket.Put(
senderInternalPubKeyKey,
contract.HtlcKeys.SenderInternalPubKey[:],
)
if err != nil {
return err
}
err = swapBucket.Put(
receiverInternalPubKeyKey,
contract.HtlcKeys.ReceiverInternalPubKey[:],
)
if err != nil {
return err
}
}
return nil
}
// unmarshalHtlcKeys deserializes the htlc keys from the swap bucket.
func unmarshalHtlcKeys(swapBucket *bbolt.Bucket, contract *SwapContract) error {
var err error
// HTLC V3 contracts have the client script key locator stored.
if contract.ProtocolVersion >= ProtocolVersionHtlcV3 &&
ProtocolVersionUnrecorded > contract.ProtocolVersion {
contract.HtlcKeys.ClientScriptKeyLocator, err =
UnmarshalKeyLocator(
swapBucket.Get(keyLocatorKey),
)
if err != nil {
return err
}
// Default the internal scriptkeys to the sender and receiver
// keys.
contract.HtlcKeys.SenderInternalPubKey =
contract.HtlcKeys.SenderScriptKey
contract.HtlcKeys.ReceiverInternalPubKey =
contract.HtlcKeys.ReceiverScriptKey
}
// MuSig2 contracts have the internal keys stored too.
if contract.ProtocolVersion >= ProtocolVersionMuSig2 &&
ProtocolVersionUnrecorded > contract.ProtocolVersion {
// The pubkeys used for the joint HTLC internal key are always
// present.
key := swapBucket.Get(senderInternalPubKeyKey)
if len(key) != keyLength {
return errInvalidKey
}
copy(contract.HtlcKeys.SenderInternalPubKey[:], key)
key = swapBucket.Get(receiverInternalPubKeyKey)
if len(key) != keyLength {
return errInvalidKey
}
copy(contract.HtlcKeys.ReceiverInternalPubKey[:], key)
}
return nil
}
// FetchLoopOutSwaps returns all loop out swaps currently in the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
@ -435,19 +545,10 @@ func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
return err
}
// Store the key locator for swaps with taproot htlc.
if swap.ProtocolVersion >= ProtocolVersionHtlcV3 {
keyLocator, err := MarshalKeyLocator(
swap.ClientKeyLocator,
)
if err != nil {
return err
}
err = swapBucket.Put(keyLocatorKey, keyLocator)
if err != nil {
return err
}
// Store the htlc keys and server key locator.
err = marshalHtlcKeys(swapBucket, &swap.SwapContract)
if err != nil {
return err
}
// Finally, we'll create an empty updates bucket for this swap
@ -501,19 +602,10 @@ func (s *boltSwapStore) CreateLoopIn(hash lntypes.Hash,
return err
}
// Store the key locator for swaps with taproot htlc.
if swap.ProtocolVersion >= ProtocolVersionHtlcV3 {
keyLocator, err := MarshalKeyLocator(
swap.ClientKeyLocator,
)
if err != nil {
return err
}
err = swapBucket.Put(keyLocatorKey, keyLocator)
if err != nil {
return err
}
// Store the htlc keys and server key locator.
err = marshalHtlcKeys(swapBucket, &swap.SwapContract)
if err != nil {
return err
}
// Finally, we'll create an empty updates bucket for this swap
@ -788,14 +880,12 @@ func (s *boltSwapStore) fetchLoopOutSwap(rootBucket *bbolt.Bucket,
return nil, err
}
// Try to unmarshal the key locator.
if contract.ProtocolVersion >= ProtocolVersionHtlcV3 {
contract.ClientKeyLocator, err = UnmarshalKeyLocator(
swapBucket.Get(keyLocatorKey),
)
if err != nil {
return nil, err
}
// Unmarshal HTLC keys if the contract is recent.
err = unmarshalHtlcKeys(
swapBucket, &contract.SwapContract,
)
if err != nil {
return nil, err
}
loop := LoopOut{
@ -865,14 +955,12 @@ func (s *boltSwapStore) fetchLoopInSwap(rootBucket *bbolt.Bucket,
return nil, err
}
// Try to unmarshal the key locator.
if contract.ProtocolVersion >= ProtocolVersionHtlcV3 {
contract.ClientKeyLocator, err = UnmarshalKeyLocator(
swapBucket.Get(keyLocatorKey),
)
if err != nil {
return nil, err
}
// Unmarshal HTLC keys if the contract is recent.
err = unmarshalHtlcKeys(
swapBucket, &contract.SwapContract,
)
if err != nil {
return nil, err
}
loop := LoopIn{

@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/coreos/bbolt"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
@ -28,6 +29,16 @@ var (
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
}
senderInternalKey = [33]byte{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4,
}
receiverInternalKey = [33]byte{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5,
}
testPreimage = lntypes.Preimage([32]byte{
1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4,
@ -51,9 +62,16 @@ func TestLoopOutStore(t *testing.T) {
AmountRequested: 100,
Preimage: testPreimage,
CltvExpiry: 144,
SenderKey: senderKey,
ReceiverKey: receiverKey,
HtlcKeys: HtlcKeys{
SenderScriptKey: senderKey,
ReceiverScriptKey: receiverKey,
SenderInternalPubKey: senderInternalKey,
ReceiverInternalPubKey: receiverInternalKey,
ClientScriptKeyLocator: keychain.KeyLocator{
Family: 1,
Index: 2,
},
},
MaxMinerFee: 10,
MaxSwapFee: 20,
@ -61,7 +79,8 @@ func TestLoopOutStore(t *testing.T) {
// Convert to/from unix to remove timezone, so that it
// doesn't interfere with DeepEqual.
InitiationTime: time.Unix(0, initiationTime.UnixNano()),
InitiationTime: time.Unix(0, initiationTime.UnixNano()),
ProtocolVersion: ProtocolVersionMuSig2,
},
MaxPrepayRoutingFee: 40,
PrepayInvoice: "prepayinvoice",
@ -195,18 +214,27 @@ func TestLoopInStore(t *testing.T) {
pendingSwap := LoopInContract{
SwapContract: SwapContract{
AmountRequested: 100,
Preimage: testPreimage,
CltvExpiry: 144,
SenderKey: senderKey,
ReceiverKey: receiverKey,
AmountRequested: 100,
Preimage: testPreimage,
CltvExpiry: 144,
HtlcKeys: HtlcKeys{
SenderScriptKey: senderKey,
ReceiverScriptKey: receiverKey,
SenderInternalPubKey: senderInternalKey,
ReceiverInternalPubKey: receiverInternalKey,
ClientScriptKeyLocator: keychain.KeyLocator{
Family: 1,
Index: 2,
},
},
MaxMinerFee: 10,
MaxSwapFee: 20,
InitiationHeight: 99,
// Convert to/from unix to remove timezone, so that it
// doesn't interfere with DeepEqual.
InitiationTime: time.Unix(0, initiationTime.UnixNano()),
InitiationTime: time.Unix(0, initiationTime.UnixNano()),
ProtocolVersion: ProtocolVersionMuSig2,
},
HtlcConfTarget: 2,
LastHop: &lastHop,

@ -237,16 +237,20 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
SwapContract: loopdb.SwapContract{
InitiationHeight: currentHeight,
InitiationTime: initiationTime,
ReceiverKey: swapResp.receiverKey,
SenderKey: senderKey,
ClientKeyLocator: keyDesc.KeyLocator,
Preimage: swapPreimage,
AmountRequested: request.Amount,
CltvExpiry: swapResp.expiry,
MaxMinerFee: request.MaxMinerFee,
MaxSwapFee: request.MaxSwapFee,
Label: request.Label,
ProtocolVersion: loopdb.CurrentProtocolVersion(),
HtlcKeys: loopdb.HtlcKeys{
SenderScriptKey: senderKey,
SenderInternalPubKey: senderKey,
ReceiverScriptKey: swapResp.receiverKey,
ReceiverInternalPubKey: swapResp.receiverKey,
ClientScriptKeyLocator: keyDesc.KeyLocator,
},
Preimage: swapPreimage,
AmountRequested: request.Amount,
CltvExpiry: swapResp.expiry,
MaxMinerFee: request.MaxMinerFee,
MaxSwapFee: request.MaxSwapFee,
Label: request.Label,
ProtocolVersion: loopdb.CurrentProtocolVersion(),
},
}
@ -975,8 +979,9 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
sequence := uint32(0)
timeoutTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, sequence, s.htlc, *htlcOutpoint, s.SenderKey,
redeemScript, witnessFunc, htlcValue, fee, s.timeoutAddr,
ctx, s.height, sequence, s.htlc, *htlcOutpoint,
s.HtlcKeys.SenderScriptKey, redeemScript, witnessFunc,
htlcValue, fee, s.timeoutAddr,
)
if err != nil {
return 0, err

@ -343,6 +343,7 @@ func TestLoopInResume(t *testing.T) {
loopdb.ProtocolVersionUnrecorded,
loopdb.ProtocolVersionHtlcV2,
loopdb.ProtocolVersionHtlcV3,
loopdb.ProtocolVersionMuSig2,
}
testCases := []struct {
@ -414,8 +415,12 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
Preimage: testPreimage,
AmountRequested: 100000,
CltvExpiry: 744,
ReceiverKey: receiverKey,
SenderKey: senderKey,
HtlcKeys: loopdb.HtlcKeys{
SenderScriptKey: senderKey,
SenderInternalPubKey: senderKey,
ReceiverScriptKey: receiverKey,
ReceiverInternalPubKey: receiverKey,
},
MaxSwapFee: 60000,
MaxMinerFee: 50000,
ProtocolVersion: storedVersion,
@ -453,17 +458,22 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
switch GetHtlcScriptVersion(storedVersion) {
case swap.HtlcV2:
htlc, err = swap.NewHtlcV2(
contract.CltvExpiry, contract.SenderKey,
contract.ReceiverKey, testPreimage.Hash(),
contract.CltvExpiry,
contract.HtlcKeys.SenderScriptKey,
contract.HtlcKeys.ReceiverScriptKey,
testPreimage.Hash(),
cfg.lnd.ChainParams,
)
case swap.HtlcV3:
htlc, err = swap.NewHtlcV3(
input.MuSig2Version040,
contract.CltvExpiry, contract.SenderKey,
contract.ReceiverKey, contract.SenderKey,
contract.ReceiverKey, testPreimage.Hash(),
contract.CltvExpiry,
contract.HtlcKeys.SenderInternalPubKey,
contract.HtlcKeys.ReceiverInternalPubKey,
contract.HtlcKeys.SenderScriptKey,
contract.HtlcKeys.ReceiverScriptKey,
testPreimage.Hash(),
cfg.lnd.ChainParams,
)

@ -181,16 +181,20 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
SwapContract: loopdb.SwapContract{
InitiationHeight: currentHeight,
InitiationTime: initiationTime,
SenderKey: swapResp.senderKey,
ReceiverKey: receiverKey,
ClientKeyLocator: keyDesc.KeyLocator,
Preimage: swapPreimage,
AmountRequested: request.Amount,
CltvExpiry: request.Expiry,
MaxMinerFee: request.MaxMinerFee,
MaxSwapFee: request.MaxSwapFee,
Label: request.Label,
ProtocolVersion: loopdb.CurrentProtocolVersion(),
HtlcKeys: loopdb.HtlcKeys{
SenderScriptKey: swapResp.senderKey,
SenderInternalPubKey: swapResp.senderKey,
ReceiverScriptKey: receiverKey,
ReceiverInternalPubKey: receiverKey,
ClientScriptKeyLocator: keyDesc.KeyLocator,
},
Preimage: swapPreimage,
AmountRequested: request.Amount,
CltvExpiry: request.Expiry,
MaxMinerFee: request.MaxMinerFee,
MaxSwapFee: request.MaxSwapFee,
Label: request.Label,
ProtocolVersion: loopdb.CurrentProtocolVersion(),
},
OutgoingChanSet: chanSet,
}
@ -1352,7 +1356,8 @@ func (s *loopOutSwap) createMuSig2SweepTxn(
}
signers := [][]byte{
s.SenderKey[1:], s.ReceiverKey[1:],
s.HtlcKeys.SenderInternalPubKey[1:],
s.HtlcKeys.ReceiverInternalPubKey[1:],
}
htlc, ok := s.htlc.HtlcScript.(*swap.HtlcScriptV3)
@ -1363,8 +1368,9 @@ func (s *loopOutSwap) createMuSig2SweepTxn(
// Now we're creating a local MuSig2 session using the receiver key's
// key locator and the htlc's root hash.
musig2SessionInfo, err := s.lnd.Signer.MuSig2CreateSession(
ctx, input.MuSig2Version040, &s.ClientKeyLocator,
signers, lndclient.MuSig2TaprootTweakOpt(htlc.RootHash[:], false),
ctx, input.MuSig2Version040,
&s.HtlcKeys.ClientScriptKeyLocator, signers,
lndclient.MuSig2TaprootTweakOpt(htlc.RootHash[:], false),
)
if err != nil {
return nil, err
@ -1609,8 +1615,8 @@ func (s *loopOutSwap) sweep(ctx context.Context, htlcOutpoint wire.OutPoint,
// Create sweep tx.
sweepTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, s.htlc.SuccessSequence(), s.htlc,
htlcOutpoint, s.ReceiverKey, redeemScript, witnessFunc,
htlcValue, fee, s.DestAddr,
htlcOutpoint, s.contract.HtlcKeys.ReceiverScriptKey,
redeemScript, witnessFunc, htlcValue, fee, s.DestAddr,
)
if err != nil {
return err

@ -76,18 +76,20 @@ func GetHtlc(hash lntypes.Hash, contract *loopdb.SwapContract,
switch GetHtlcScriptVersion(contract.ProtocolVersion) {
case swap.HtlcV2:
return swap.NewHtlcV2(
contract.CltvExpiry, contract.SenderKey,
contract.ReceiverKey, hash,
contract.CltvExpiry, contract.HtlcKeys.SenderScriptKey,
contract.HtlcKeys.ReceiverScriptKey, hash,
chainParams,
)
case swap.HtlcV3:
return swap.NewHtlcV3(
input.MuSig2Version040,
contract.CltvExpiry, contract.SenderKey,
contract.ReceiverKey, contract.SenderKey,
contract.ReceiverKey, hash,
chainParams,
contract.CltvExpiry,
contract.HtlcKeys.SenderInternalPubKey,
contract.HtlcKeys.ReceiverInternalPubKey,
contract.HtlcKeys.SenderScriptKey,
contract.HtlcKeys.ReceiverScriptKey,
hash, chainParams,
)
}

Loading…
Cancel
Save