mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
staticaddr/address: activate derived addresses
Create receive and change addresses from locally derived client keys while reusing the server key and expiry from the legacy seed. Import, persist, and activate each script before returning it to callers.
This commit is contained in:
parent
29f5ce73ce
commit
a56621cf47
7 changed files with 429 additions and 120 deletions
|
|
@ -1883,7 +1883,7 @@ func (s *swapClientServer) ListUnspentDeposits(ctx context.Context,
|
|||
|
||||
// List all unspent utxos the wallet sees, regardless of the number of
|
||||
// confirmations.
|
||||
staticAddress, utxos, err := s.staticAddressManager.ListUnspentRaw(
|
||||
utxos, err := s.staticAddressManager.ListUnspentRaw(
|
||||
ctx, req.MinConfs, req.MaxConfs,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -1934,6 +1934,20 @@ func (s *swapClientServer) ListUnspentDeposits(ctx context.Context,
|
|||
continue
|
||||
}
|
||||
|
||||
params := s.staticAddressManager.GetParameters(u.PkScript)
|
||||
if params == nil {
|
||||
return nil, fmt.Errorf("missing static address "+
|
||||
"parameters for %v", u.OutPoint)
|
||||
}
|
||||
|
||||
staticAddress, err := s.staticAddressManager.GetTaprootAddress(
|
||||
params.ClientPubkey, params.ServerPubkey,
|
||||
int64(params.Expiry),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utxo := &looprpc.Utxo{
|
||||
StaticAddress: staticAddress.String(),
|
||||
AmountSat: int64(u.Value),
|
||||
|
|
|
|||
|
|
@ -62,12 +62,44 @@ func (s *staticAddrDepositStore) AllDeposits(context.Context) (
|
|||
return s.allDeposits, nil
|
||||
}
|
||||
|
||||
type staticAddrTestAddressManager struct{}
|
||||
type staticAddrTestAddressManager struct {
|
||||
params *address.Parameters
|
||||
}
|
||||
|
||||
func newStaticAddrTestAddressManager() *staticAddrTestAddressManager {
|
||||
_, client := mock_lnd.CreateKey(1)
|
||||
_, server := mock_lnd.CreateKey(2)
|
||||
|
||||
return &staticAddrTestAddressManager{
|
||||
params: &address.Parameters{
|
||||
ID: 1,
|
||||
ClientPubkey: client,
|
||||
ServerPubkey: server,
|
||||
Expiry: 10,
|
||||
PkScript: []byte("pkscript"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *staticAddrTestAddressManager) GetStaticAddressParameters(
|
||||
context.Context) (*script.Parameters, error) {
|
||||
|
||||
return nil, nil
|
||||
return s.params, nil
|
||||
}
|
||||
|
||||
func (s *staticAddrTestAddressManager) GetStaticAddressID(
|
||||
context.Context, []byte) (int32, error) {
|
||||
|
||||
return s.params.ID, nil
|
||||
}
|
||||
|
||||
func (s *staticAddrTestAddressManager) GetParameters(
|
||||
pkScript []byte) *address.Parameters {
|
||||
|
||||
params := *s.params
|
||||
params.PkScript = pkScript
|
||||
|
||||
return ¶ms
|
||||
}
|
||||
|
||||
func (s *staticAddrTestAddressManager) GetStaticAddress(
|
||||
|
|
@ -99,7 +131,7 @@ func newTestDepositManager(
|
|||
}
|
||||
|
||||
return deposit.NewManager(&deposit.ManagerConfig{
|
||||
AddressManager: &staticAddrTestAddressManager{},
|
||||
AddressManager: newStaticAddrTestAddressManager(),
|
||||
Store: &staticAddrDepositStore{
|
||||
allDeposits: deposits,
|
||||
byOutpoint: byOutpoint,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package loopd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
|
@ -1883,10 +1885,25 @@ type mockAddressStore struct {
|
|||
func (s *mockAddressStore) CreateStaticAddress(_ context.Context,
|
||||
p *script.Parameters) error {
|
||||
|
||||
if p.ID == 0 {
|
||||
p.ID = int32(len(s.params) + 1)
|
||||
}
|
||||
s.params = append(s.params, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mockAddressStore) GetStaticAddressID(_ context.Context,
|
||||
pkScript []byte) (int32, error) {
|
||||
|
||||
for _, p := range s.params {
|
||||
if bytes.Equal(p.PkScript, pkScript) {
|
||||
return p.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (s *mockAddressStore) GetStaticAddress(_ context.Context, _ []byte) (
|
||||
*script.Parameters, error) {
|
||||
|
||||
|
|
@ -1903,6 +1920,16 @@ func (s *mockAddressStore) GetAllStaticAddresses(_ context.Context) (
|
|||
return s.params, nil
|
||||
}
|
||||
|
||||
func (s *mockAddressStore) GetLegacyParameters(_ context.Context) (
|
||||
*address.Parameters, error) {
|
||||
|
||||
if len(s.params) == 0 {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
return s.params[0], nil
|
||||
}
|
||||
|
||||
// mockDepositStore implements deposit.Store minimally for DepositsForOutpoints.
|
||||
type mockDepositStore struct {
|
||||
byOutpoint map[string]*deposit.Deposit
|
||||
|
|
@ -2038,7 +2065,12 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
// Prepare a single static address parameter set.
|
||||
_, client := mock_lnd.CreateKey(1)
|
||||
_, server := mock_lnd.CreateKey(2)
|
||||
pkScript := []byte("pkscript")
|
||||
staticAddress, err := script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, 10, client, server,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
pkScript, err := staticAddress.StaticAddressScript()
|
||||
require.NoError(t, err)
|
||||
addrParams := &script.Parameters{
|
||||
ClientPubkey: client,
|
||||
ServerPubkey: server,
|
||||
|
|
@ -2056,6 +2088,8 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
// ChainNotifier and AddressClient are not needed for this test.
|
||||
}, 1)
|
||||
require.NoError(t, err)
|
||||
_, err = addrMgr.EnsureStaticAddressSeed(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Construct several UTXOs with different confirmation counts.
|
||||
makeUtxo := func(idx uint32, confs int64) *lnwallet.Utxo {
|
||||
|
|
|
|||
|
|
@ -6,15 +6,26 @@ import (
|
|||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
)
|
||||
|
||||
// Parameters aliases the script-level static address parameters for callers
|
||||
// that interact with the address manager API.
|
||||
type Parameters = script.Parameters
|
||||
|
||||
// Store is the database interface that is used to store and retrieve
|
||||
// static addresses.
|
||||
type Store interface {
|
||||
// CreateStaticAddress inserts a new static address with its parameters
|
||||
// into the store.
|
||||
CreateStaticAddress(ctx context.Context,
|
||||
addrParams *script.Parameters) error
|
||||
CreateStaticAddress(ctx context.Context, addrParams *Parameters) error
|
||||
|
||||
// GetStaticAddressID retrieves the static address row ID for the
|
||||
// address script.
|
||||
GetStaticAddressID(ctx context.Context, pkScript []byte) (int32, error)
|
||||
|
||||
// GetAllStaticAddresses retrieves all static addresses from the store.
|
||||
GetAllStaticAddresses(ctx context.Context) ([]*script.Parameters,
|
||||
error)
|
||||
GetAllStaticAddresses(ctx context.Context) ([]*Parameters, error)
|
||||
|
||||
// GetLegacyParameters retrieves the first static address created for the
|
||||
// L402. This is the immutable legacy/root address that anchors existing
|
||||
// single-address deposits.
|
||||
GetLegacyParameters(ctx context.Context) (*Parameters, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package address
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
|
|
@ -29,6 +31,12 @@ const (
|
|||
maxStaticAddressCSVExpiry = uint32(200 * 144)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoStaticAddress is returned when no static address parameters are
|
||||
// present in the store.
|
||||
ErrNoStaticAddress = errors.New("no static address parameters found")
|
||||
)
|
||||
|
||||
// ManagerConfig holds the configuration for the address manager.
|
||||
type ManagerConfig struct {
|
||||
// AddressClient is the client that communicates with the loop server
|
||||
|
|
@ -62,6 +70,12 @@ type Manager struct {
|
|||
cfg *ManagerConfig
|
||||
|
||||
currentHeight atomic.Int32
|
||||
|
||||
// activeStaticAddresses is the runtime index used to match wallet UTXOs
|
||||
// to locally known static address parameters. The DB remains the
|
||||
// durable source of truth; this map is rebuilt from the DB on startup
|
||||
// and updated after successful address issuance.
|
||||
activeStaticAddresses map[string]*Parameters
|
||||
}
|
||||
|
||||
// NewManager creates a new address manager.
|
||||
|
|
@ -72,7 +86,8 @@ func NewManager(cfg *ManagerConfig, currentHeight int32) (*Manager, error) {
|
|||
}
|
||||
|
||||
m := &Manager{
|
||||
cfg: cfg,
|
||||
cfg: cfg,
|
||||
activeStaticAddresses: make(map[string]*Parameters),
|
||||
}
|
||||
m.currentHeight.Store(currentHeight)
|
||||
|
||||
|
|
@ -88,6 +103,11 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = m.loadActiveAddresses(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Communicate to the caller that the address manager has completed its
|
||||
// initialization.
|
||||
close(initChan)
|
||||
|
|
@ -107,54 +127,125 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
// NewAddress creates a new static address with the server or returns an
|
||||
// existing one.
|
||||
// loadActiveAddresses rebuilds the runtime address map from the durable DB
|
||||
// state and re-imports all scripts into lnd. Importing is intentionally
|
||||
// idempotent so restart paths repair missing wallet watches before deposit
|
||||
// discovery starts.
|
||||
func (m *Manager) loadActiveAddresses(ctx context.Context) error {
|
||||
params, err := m.cfg.Store.GetAllStaticAddresses(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
active := make(map[string]*Parameters, len(params))
|
||||
for _, param := range params {
|
||||
staticAddress, err := staticAddressFromParams(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.importAddressTapscript(ctx, staticAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
active[string(param.PkScript)] = param
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
m.activeStaticAddresses = active
|
||||
m.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAddress creates the next externally visible receive static address.
|
||||
//
|
||||
// The first call also makes sure the legacy/root static address seed exists,
|
||||
// because receive and change addresses are derived from the server pubkey and
|
||||
// expiry returned for that seed.
|
||||
func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
|
||||
int64, error) {
|
||||
|
||||
// If there's already a static address in the database, we can return
|
||||
// it.
|
||||
params, err := m.NewReceiveAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
address, err := m.GetTaprootAddress(
|
||||
params.ClientPubkey, params.ServerPubkey, int64(params.Expiry),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return address, int64(params.Expiry), nil
|
||||
}
|
||||
|
||||
// EnsureStaticAddressSeed loads or creates the legacy/root static address
|
||||
// parameters. The root address is the only address that requires a Nautilus
|
||||
// ServerNewAddress call; all receive/change addresses derive client keys
|
||||
// locally and reuse this server pubkey/expiry seed.
|
||||
func (m *Manager) EnsureStaticAddressSeed(ctx context.Context) (*Parameters,
|
||||
error) {
|
||||
|
||||
m.Lock()
|
||||
seed := m.legacyParameters()
|
||||
m.Unlock()
|
||||
if seed != nil {
|
||||
return seed, nil
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// Another caller may have created the seed while we were waiting for the
|
||||
// issuance lock.
|
||||
seed = m.legacyParameters()
|
||||
if seed != nil {
|
||||
return seed, nil
|
||||
}
|
||||
|
||||
addresses, err := m.cfg.Store.GetAllStaticAddresses(ctx)
|
||||
if err != nil {
|
||||
m.Unlock()
|
||||
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
if len(addresses) > 0 {
|
||||
clientPubKey := addresses[0].ClientPubkey
|
||||
serverPubKey := addresses[0].ServerPubkey
|
||||
expiry := int64(addresses[0].Expiry)
|
||||
for _, addr := range addresses {
|
||||
// Re-import existing rows so startup can repair a DB-only
|
||||
// address before deposit discovery depends on lnd's wallet
|
||||
// view.
|
||||
staticAddress, err := staticAddressFromParams(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer m.Unlock()
|
||||
err = m.importAddressTapscript(ctx, staticAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
address, err := m.GetTaprootAddress(
|
||||
clientPubKey, serverPubKey, expiry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
m.activeStaticAddresses[string(addr.PkScript)] = addr
|
||||
}
|
||||
|
||||
return address, expiry, nil
|
||||
return addresses[0], nil
|
||||
}
|
||||
m.Unlock()
|
||||
|
||||
// We are fetching a new L402 token from the server. There is one static
|
||||
// address per L402 token allowed.
|
||||
// We are fetching a new L402 token from the server. The returned server
|
||||
// key/expiry is the static address seed for all future client-derived
|
||||
// addresses for this L402.
|
||||
err = m.cfg.FetchL402(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientPubKey, err := m.cfg.WalletKit.DeriveNextKey(
|
||||
ctx, swap.StaticAddressKeyFamily,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
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 := version.CurrentRPCProtocolVersion()
|
||||
resp, err := m.cfg.AddressClient.ServerNewAddress(
|
||||
ctx, &staticaddressrpc.ServerNewAddressRequest{
|
||||
|
|
@ -163,78 +254,121 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
|
|||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
return nil, 0, fmt.Errorf("missing server new address response")
|
||||
return nil, fmt.Errorf("missing server new address response")
|
||||
}
|
||||
|
||||
serverParams := resp.GetParams()
|
||||
if err := validateServerAddressParams(serverParams); err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverPubKey, err := btcec.ParsePubKey(serverParams.GetServerKey())
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.createAddressFromKey(
|
||||
ctx, clientPubKey, serverPubKey, serverParams.Expiry,
|
||||
version.AddressProtocolVersion(protocolVersion),
|
||||
)
|
||||
}
|
||||
|
||||
// NewReceiveAddress derives, stores, imports and activates the next receive
|
||||
// family static address. It is used by `loop static new`.
|
||||
func (m *Manager) NewReceiveAddress(ctx context.Context) (*Parameters, error) {
|
||||
seed, err := m.EnsureStaticAddressSeed(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.newDerivedAddress(ctx, seed, swap.StaticMultiAddressKeyFamily)
|
||||
}
|
||||
|
||||
// NewChangeAddress derives, stores, imports and activates the next change
|
||||
// family static address. Swap and withdrawal code calls this before submitting
|
||||
// requests that require change.
|
||||
func (m *Manager) NewChangeAddress(ctx context.Context) (*Parameters, error) {
|
||||
seed, err := m.EnsureStaticAddressSeed(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.newDerivedAddress(ctx, seed, swap.StaticAddressChangeKeyFamily)
|
||||
}
|
||||
|
||||
func (m *Manager) newDerivedAddress(ctx context.Context, seed *Parameters,
|
||||
keyFamily int32) (*Parameters, error) {
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
clientPubKey, err := m.cfg.WalletKit.DeriveNextKey(ctx, keyFamily)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.createAddressFromKey(
|
||||
ctx, clientPubKey, seed.ServerPubkey, seed.Expiry,
|
||||
seed.ProtocolVersion,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Manager) createAddressFromKey(ctx context.Context,
|
||||
clientPubKey *keychain.KeyDescriptor, serverPubKey *btcec.PublicKey,
|
||||
expiry uint32, protocolVersion version.AddressProtocolVersion) (
|
||||
*Parameters, error) {
|
||||
|
||||
staticAddress, err := script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, int64(serverParams.Expiry),
|
||||
clientPubKey.PubKey, serverPubKey,
|
||||
input.MuSig2Version100RC2, int64(expiry), clientPubKey.PubKey,
|
||||
serverPubKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkScript, err := staticAddress.StaticAddressScript()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the static address from the parameters the server provided and
|
||||
// store all parameters in the database.
|
||||
addrParams := &script.Parameters{
|
||||
addrParams := &Parameters{
|
||||
ClientPubkey: clientPubKey.PubKey,
|
||||
ServerPubkey: serverPubKey,
|
||||
PkScript: pkScript,
|
||||
Expiry: serverParams.Expiry,
|
||||
Expiry: expiry,
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: clientPubKey.Family,
|
||||
Index: clientPubKey.Index,
|
||||
},
|
||||
ProtocolVersion: version.AddressProtocolVersion(
|
||||
protocolVersion,
|
||||
),
|
||||
ProtocolVersion: protocolVersion,
|
||||
InitiationHeight: m.currentHeight.Load(),
|
||||
}
|
||||
|
||||
// Import before persisting the address row. If lnd rejects the script
|
||||
// import, a later startup retry should still see a clean missing-address
|
||||
// state instead of a DB-only static address.
|
||||
err = m.importAddressTapscript(ctx, staticAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = m.cfg.Store.CreateStaticAddress(ctx, addrParams)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
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)
|
||||
addrParams.ID, err = m.cfg.Store.GetStaticAddressID(ctx, pkScript)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("Imported static address taproot script to lnd wallet: %v",
|
||||
addr)
|
||||
m.activeStaticAddresses[string(pkScript)] = addrParams
|
||||
|
||||
address, err := m.GetTaprootAddress(
|
||||
clientPubKey.PubKey, serverPubKey, int64(serverParams.Expiry),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return address, int64(serverParams.Expiry), nil
|
||||
return addrParams, nil
|
||||
}
|
||||
|
||||
// validateServerAddressParams validates the server-controlled static address
|
||||
|
|
@ -272,6 +406,60 @@ func validateServerAddressParams(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) importAddressTapscript(ctx context.Context,
|
||||
staticAddress *script.StaticAddress) error {
|
||||
|
||||
// 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 {
|
||||
// Importing into an lnd instance that already knows the script is
|
||||
// expected on restart. Treat the duplicate import as success.
|
||||
if strings.Contains(err.Error(), "already exists") {
|
||||
log.Infof("Static address tapscript already imported")
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Imported static address taproot script to lnd wallet: %v",
|
||||
addr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func staticAddressFromParams(params *Parameters) (*script.StaticAddress,
|
||||
error) {
|
||||
|
||||
if params == nil {
|
||||
return nil, fmt.Errorf("missing static address parameters")
|
||||
}
|
||||
|
||||
return script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, int64(params.Expiry),
|
||||
params.ClientPubkey, params.ServerPubkey,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Manager) legacyParameters() *Parameters {
|
||||
var legacy *Parameters
|
||||
for _, params := range m.activeStaticAddresses {
|
||||
if params == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if legacy == nil || params.ID < legacy.ID {
|
||||
legacy = params
|
||||
}
|
||||
}
|
||||
|
||||
return legacy
|
||||
}
|
||||
|
||||
// GetTaprootAddress returns a taproot address for the given client and server
|
||||
// public keys and expiry.
|
||||
func (m *Manager) GetTaprootAddress(clientPubkey, serverPubkey *btcec.PublicKey,
|
||||
|
|
@ -292,21 +480,17 @@ func (m *Manager) GetTaprootAddress(clientPubkey, serverPubkey *btcec.PublicKey,
|
|||
|
||||
// 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) {
|
||||
maxConfs int32) ([]*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")
|
||||
m.Lock()
|
||||
active := make(map[string]struct{}, len(m.activeStaticAddresses))
|
||||
for pkScript := range m.activeStaticAddresses {
|
||||
active[pkScript] = struct{}{}
|
||||
}
|
||||
m.Unlock()
|
||||
if len(active) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
staticAddress := addresses[0]
|
||||
|
||||
// List all unspent utxos the wallet sees, regardless of the number of
|
||||
// confirmations.
|
||||
|
|
@ -314,43 +498,36 @@ func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
|
|||
ctx, minConfs, maxConfs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter the list of lnd's unspent utxos for the pkScript of our static
|
||||
// address.
|
||||
// Filter the list of lnd's unspent utxos for any locally active static
|
||||
// address script.
|
||||
var filteredUtxos []*lnwallet.Utxo
|
||||
for _, utxo := range utxos {
|
||||
if bytes.Equal(utxo.PkScript, staticAddress.PkScript) {
|
||||
if _, ok := active[string(utxo.PkScript)]; ok {
|
||||
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
|
||||
return filteredUtxos, nil
|
||||
}
|
||||
|
||||
// GetStaticAddressParameters returns the parameters of the static address.
|
||||
// GetStaticAddressParameters returns the legacy/root static-address
|
||||
// parameters.
|
||||
func (m *Manager) GetStaticAddressParameters(ctx context.Context) (
|
||||
*script.Parameters, error) {
|
||||
|
||||
params, err := m.cfg.Store.GetAllStaticAddresses(ctx)
|
||||
params, err := m.GetLegacyParameters(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
return nil, fmt.Errorf("no static address parameters found")
|
||||
if params == nil {
|
||||
return nil, ErrNoStaticAddress
|
||||
}
|
||||
|
||||
return params[0], nil
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetStaticAddress returns a taproot address for the given client and server
|
||||
|
|
@ -363,25 +540,53 @@ func (m *Manager) GetStaticAddress(ctx context.Context) (*script.StaticAddress,
|
|||
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
|
||||
return staticAddressFromParams(params)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return m.ListUnspentRaw(ctx, minConfs, maxConfs)
|
||||
}
|
||||
|
||||
// GetLegacyParameters returns the legacy/root static address parameters.
|
||||
func (m *Manager) GetLegacyParameters(ctx context.Context) (*Parameters,
|
||||
error) {
|
||||
|
||||
params, err := m.cfg.Store.GetLegacyParameters(ctx)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return utxos, nil
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParameters returns active static address parameters for a pkScript.
|
||||
func (m *Manager) GetParameters(pkScript []byte) *Parameters {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return m.activeStaticAddresses[string(pkScript)]
|
||||
}
|
||||
|
||||
// GetStaticAddressID returns the database row ID for a static address script.
|
||||
func (m *Manager) GetStaticAddressID(ctx context.Context,
|
||||
pkScript []byte) (int32, error) {
|
||||
|
||||
return m.cfg.Store.GetStaticAddressID(ctx, pkScript)
|
||||
}
|
||||
|
||||
// IsOurPkScript returns true if the pkScript belongs to an active static
|
||||
// address.
|
||||
func (m *Manager) IsOurPkScript(pkScript []byte) bool {
|
||||
return m.GetParameters(pkScript) != nil
|
||||
}
|
||||
|
||||
// GetAllAddresses returns all persisted static address parameters.
|
||||
func (m *Manager) GetAllAddresses(ctx context.Context) ([]*Parameters, error) {
|
||||
return m.cfg.Store.GetAllStaticAddresses(ctx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,20 @@ func TestManager(t *testing.T) {
|
|||
|
||||
// The expiry has to match.
|
||||
require.EqualValues(t, defaultExpiry, expiry)
|
||||
|
||||
storedParams, err := testContext.manager.GetStaticAddressParameters(ctxb)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(
|
||||
t, swap.StaticAddressKeyFamily, storedParams.KeyLocator.Family,
|
||||
)
|
||||
|
||||
addresses, err := testContext.manager.GetAllAddresses(ctxb)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, addresses, 2)
|
||||
require.EqualValues(
|
||||
t, swap.StaticMultiAddressKeyFamily,
|
||||
addresses[1].KeyLocator.Family,
|
||||
)
|
||||
}
|
||||
|
||||
// TestNewAddressValidatesServerResponse tests that the untrusted
|
||||
|
|
@ -233,12 +247,12 @@ func TestNewAddressAcceptsMaxCSVExpiry(t *testing.T) {
|
|||
func GenerateExpectedTaprootAddress(t *ManagerTestContext) (
|
||||
*btcutil.AddressTaproot, error) {
|
||||
|
||||
keyIndex := int32(0)
|
||||
keyIndex := int32(1)
|
||||
_, pubKey := test.CreateKey(keyIndex)
|
||||
|
||||
keyDescriptor := &keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(swap.StaticAddressKeyFamily),
|
||||
Family: keychain.KeyFamily(swap.StaticMultiAddressKeyFamily),
|
||||
Index: uint32(keyIndex),
|
||||
},
|
||||
PubKey: pubKey,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/loopdb/sqlc"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/staticaddr/version"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
|
@ -26,7 +25,7 @@ func NewSqlStore(db *loopdb.BaseDB) *SqlStore {
|
|||
|
||||
// CreateStaticAddress creates a static address record in the database.
|
||||
func (s *SqlStore) CreateStaticAddress(ctx context.Context,
|
||||
addrParams *script.Parameters) error {
|
||||
addrParams *Parameters) error {
|
||||
|
||||
createArgs := sqlc.CreateStaticAddressParams{
|
||||
ClientPubkey: addrParams.ClientPubkey.SerializeCompressed(),
|
||||
|
|
@ -51,14 +50,14 @@ func (s *SqlStore) GetStaticAddressID(ctx context.Context,
|
|||
|
||||
// GetAllStaticAddresses returns all addresses known to the client.
|
||||
func (s *SqlStore) GetAllStaticAddresses(ctx context.Context) (
|
||||
[]*script.Parameters, error) {
|
||||
[]*Parameters, error) {
|
||||
|
||||
staticAddresses, err := s.baseDB.Queries.AllStaticAddresses(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []*script.Parameters
|
||||
var result []*Parameters
|
||||
for _, address := range staticAddresses {
|
||||
res, err := s.toAddressParameters(address)
|
||||
if err != nil {
|
||||
|
|
@ -72,8 +71,8 @@ func (s *SqlStore) GetAllStaticAddresses(ctx context.Context) (
|
|||
}
|
||||
|
||||
// GetLegacyParameters returns the first static address created for this L402.
|
||||
func (s *SqlStore) GetLegacyParameters(ctx context.Context) (
|
||||
*script.Parameters, error) {
|
||||
func (s *SqlStore) GetLegacyParameters(ctx context.Context) (*Parameters,
|
||||
error) {
|
||||
|
||||
staticAddress, err := s.baseDB.Queries.GetLegacyAddress(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -86,7 +85,7 @@ func (s *SqlStore) GetLegacyParameters(ctx context.Context) (
|
|||
// toAddressParameters transforms a database representation of a static address
|
||||
// to an AddressParameters struct.
|
||||
func (s *SqlStore) toAddressParameters(row sqlc.StaticAddress) (
|
||||
*script.Parameters, error) {
|
||||
*Parameters, error) {
|
||||
|
||||
clientPubkey, err := btcec.ParsePubKey(row.ClientPubkey)
|
||||
if err != nil {
|
||||
|
|
@ -98,7 +97,7 @@ func (s *SqlStore) toAddressParameters(row sqlc.StaticAddress) (
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &script.Parameters{
|
||||
return &Parameters{
|
||||
ID: row.ID,
|
||||
ClientPubkey: clientPubkey,
|
||||
ServerPubkey: serverPubkey,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue