diff --git a/staticaddr/manager.go b/staticaddr/manager.go new file mode 100644 index 0000000..04dd1d5 --- /dev/null +++ b/staticaddr/manager.go @@ -0,0 +1,188 @@ +package staticaddr + +import ( + "context" + "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/lightninglabs/lndclient" + "github.com/lightninglabs/loop" + "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" +) + +// 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 + + // SwapClient provides loop rpc functionality. + SwapClient *loop.Client + + // Store is the database store that is used to store static address + // related records. + Store AddressStore + + // 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 + + initChan chan struct{} + + sync.Mutex +} + +// NewAddressManager creates a new address manager. +func NewAddressManager(cfg *ManagerConfig) *Manager { + return &Manager{ + cfg: cfg, + initChan: make(chan struct{}), + } +} + +// Run runs the address manager. +func (m *Manager) Run(ctx context.Context) error { + log.Debugf("Starting address manager.") + defer log.Debugf("Address manager stopped.") + + // Communicate to the caller that the address manager has completed its + // initialization. + close(m.initChan) + + <-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.SwapClient.Server.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 := 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 := &AddressParameters{ + ClientPubkey: clientPubKey.PubKey, + ServerPubkey: serverPubKey, + PkScript: pkScript, + Expiry: serverParams.Expiry, + KeyLocator: keychain.KeyLocator{ + Family: clientPubKey.Family, + Index: clientPubKey.Index, + }, + ProtocolVersion: AddressProtocolVersion(protocolVersion), + } + err = m.cfg.Store.CreateStaticAddress(ctx, addrParams) + if err != nil { + return nil, err + } + + 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, + ) +} + +// WaitInitComplete waits until the address manager has completed its setup. +func (m *Manager) WaitInitComplete() { + defer log.Debugf("Address manager initiation complete.") + <-m.initChan +}