mirror of https://github.com/lightninglabs/loop
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
7.1 KiB
Go
292 lines
7.1 KiB
Go
package address
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btclog"
|
|
"github.com/lightninglabs/lndclient"
|
|
"github.com/lightninglabs/loop/staticaddr"
|
|
"github.com/lightninglabs/loop/staticaddr/script"
|
|
"github.com/lightninglabs/loop/swap"
|
|
staticaddressrpc "github.com/lightninglabs/loop/swapserverrpc"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
)
|
|
|
|
var (
|
|
log btclog.Logger
|
|
)
|
|
|
|
// ManagerConfig holds the configuration for the address manager.
|
|
type ManagerConfig struct {
|
|
// AddressClient is the client that communicates with the loop server
|
|
// to manage static addresses.
|
|
AddressClient staticaddressrpc.StaticAddressServerClient
|
|
|
|
// FetchL402 is the function used to fetch the l402 token.
|
|
FetchL402 func(context.Context) error
|
|
|
|
// Store is the database store that is used to store static address
|
|
// related records.
|
|
Store Store
|
|
|
|
// WalletKit is the wallet client that is used to derive new keys from
|
|
// lnd's wallet.
|
|
WalletKit lndclient.WalletKitClient
|
|
|
|
// ChainParams is the chain configuration(mainnet, testnet...) this
|
|
// manager uses.
|
|
ChainParams *chaincfg.Params
|
|
}
|
|
|
|
// Manager manages the address state machines.
|
|
type Manager struct {
|
|
cfg *ManagerConfig
|
|
|
|
sync.Mutex
|
|
}
|
|
|
|
// NewManager creates a new address manager.
|
|
func NewManager(cfg *ManagerConfig) *Manager {
|
|
log = staticaddr.GetLogger()
|
|
return &Manager{
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
// Run runs the address manager.
|
|
func (m *Manager) Run(ctx context.Context) error {
|
|
<-ctx.Done()
|
|
return nil
|
|
}
|
|
|
|
// NewAddress starts a new address creation flow.
|
|
func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
|
|
error) {
|
|
|
|
// If there's already a static address in the database, we can return
|
|
// it.
|
|
m.Lock()
|
|
addresses, err := m.cfg.Store.GetAllStaticAddresses(ctx)
|
|
if err != nil {
|
|
m.Unlock()
|
|
|
|
return nil, err
|
|
}
|
|
if len(addresses) > 0 {
|
|
clientPubKey := addresses[0].ClientPubkey
|
|
serverPubKey := addresses[0].ServerPubkey
|
|
expiry := int64(addresses[0].Expiry)
|
|
|
|
m.Unlock()
|
|
|
|
return m.getTaprootAddress(clientPubKey, serverPubKey, expiry)
|
|
}
|
|
m.Unlock()
|
|
|
|
// We are fetching a new L402 token from the server. There is one static
|
|
// address per L402 token allowed.
|
|
err = m.cfg.FetchL402(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clientPubKey, err := m.cfg.WalletKit.DeriveNextKey(
|
|
ctx, swap.StaticAddressKeyFamily,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Send our clientPubKey to the server and wait for the server to
|
|
// respond with he serverPubKey and the static address CSV expiry.
|
|
protocolVersion := staticaddr.CurrentRPCProtocolVersion()
|
|
resp, err := m.cfg.AddressClient.ServerNewAddress(
|
|
ctx, &staticaddressrpc.ServerNewAddressRequest{
|
|
ProtocolVersion: protocolVersion,
|
|
ClientKey: clientPubKey.PubKey.SerializeCompressed(), //nolint:lll
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
serverParams := resp.GetParams()
|
|
|
|
serverPubKey, err := btcec.ParsePubKey(serverParams.ServerKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
staticAddress, err := script.NewStaticAddress(
|
|
input.MuSig2Version100RC2, int64(serverParams.Expiry),
|
|
clientPubKey.PubKey, serverPubKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pkScript, err := staticAddress.StaticAddressScript()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create the static address from the parameters the server provided and
|
|
// store all parameters in the database.
|
|
addrParams := &Parameters{
|
|
ClientPubkey: clientPubKey.PubKey,
|
|
ServerPubkey: serverPubKey,
|
|
PkScript: pkScript,
|
|
Expiry: serverParams.Expiry,
|
|
KeyLocator: keychain.KeyLocator{
|
|
Family: clientPubKey.Family,
|
|
Index: clientPubKey.Index,
|
|
},
|
|
ProtocolVersion: staticaddr.AddressProtocolVersion(
|
|
protocolVersion,
|
|
),
|
|
}
|
|
err = m.cfg.Store.CreateStaticAddress(ctx, addrParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Import the static address tapscript into our lnd wallet, so we can
|
|
// track unspent outputs of it.
|
|
tapScript := input.TapscriptFullTree(
|
|
staticAddress.InternalPubKey, *staticAddress.TimeoutLeaf,
|
|
)
|
|
addr, err := m.cfg.WalletKit.ImportTaprootScript(ctx, tapScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("Imported static address taproot script to lnd wallet: %v",
|
|
addr)
|
|
|
|
return m.getTaprootAddress(
|
|
clientPubKey.PubKey, serverPubKey, int64(serverParams.Expiry),
|
|
)
|
|
}
|
|
|
|
func (m *Manager) getTaprootAddress(clientPubkey,
|
|
serverPubkey *btcec.PublicKey, expiry int64) (*btcutil.AddressTaproot,
|
|
error) {
|
|
|
|
staticAddress, err := script.NewStaticAddress(
|
|
input.MuSig2Version100RC2, expiry, clientPubkey, serverPubkey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return btcutil.NewAddressTaproot(
|
|
schnorr.SerializePubKey(staticAddress.TaprootKey),
|
|
m.cfg.ChainParams,
|
|
)
|
|
}
|
|
|
|
// ListUnspentRaw returns a list of utxos at the static address.
|
|
func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
|
|
maxConfs int32) (*btcutil.AddressTaproot, []*lnwallet.Utxo, error) {
|
|
|
|
addresses, err := m.cfg.Store.GetAllStaticAddresses(ctx)
|
|
switch {
|
|
case err != nil:
|
|
return nil, nil, err
|
|
|
|
case len(addresses) == 0:
|
|
return nil, nil, nil
|
|
|
|
case len(addresses) > 1:
|
|
return nil, nil, fmt.Errorf("more than one address found")
|
|
}
|
|
|
|
staticAddress := addresses[0]
|
|
|
|
// List all unspent utxos the wallet sees, regardless of the number of
|
|
// confirmations.
|
|
utxos, err := m.cfg.WalletKit.ListUnspent(
|
|
ctx, minConfs, maxConfs,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Filter the list of lnd's unspent utxos for the pkScript of our static
|
|
// address.
|
|
var filteredUtxos []*lnwallet.Utxo
|
|
for _, utxo := range utxos {
|
|
if bytes.Equal(utxo.PkScript, staticAddress.PkScript) {
|
|
filteredUtxos = append(filteredUtxos, utxo)
|
|
}
|
|
}
|
|
|
|
taprootAddress, err := m.getTaprootAddress(
|
|
staticAddress.ClientPubkey, staticAddress.ServerPubkey,
|
|
int64(staticAddress.Expiry),
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return taprootAddress, filteredUtxos, nil
|
|
}
|
|
|
|
// GetStaticAddressParameters returns the parameters of the static address.
|
|
func (m *Manager) GetStaticAddressParameters(ctx context.Context) (*Parameters,
|
|
error) {
|
|
|
|
params, err := m.cfg.Store.GetAllStaticAddresses(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(params) == 0 {
|
|
return nil, fmt.Errorf("no static address parameters found")
|
|
}
|
|
|
|
return params[0], nil
|
|
}
|
|
|
|
// GetStaticAddress returns a taproot address for the given client and server
|
|
// public keys and expiry.
|
|
func (m *Manager) GetStaticAddress(ctx context.Context) (*script.StaticAddress,
|
|
error) {
|
|
|
|
params, err := m.GetStaticAddressParameters(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
address, err := script.NewStaticAddress(
|
|
input.MuSig2Version100RC2, int64(params.Expiry),
|
|
params.ClientPubkey, params.ServerPubkey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return address, nil
|
|
}
|
|
|
|
// ListUnspent returns a list of utxos at the static address.
|
|
func (m *Manager) ListUnspent(ctx context.Context, minConfs,
|
|
maxConfs int32) ([]*lnwallet.Utxo, error) {
|
|
|
|
_, utxos, err := m.ListUnspentRaw(ctx, minConfs, maxConfs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return utxos, nil
|
|
}
|