@@ -3,7 +3,9 @@ package address
33import (
44 "bytes"
55 "context"
6+ "errors"
67 "fmt"
8+ "strings"
79 "sync"
810 "sync/atomic"
911
@@ -21,6 +23,12 @@ import (
2123 "github.com/lightningnetwork/lnd/lnwallet"
2224)
2325
26+ var (
27+ // ErrNoStaticAddress is returned when no static address parameters are
28+ // present in the store.
29+ ErrNoStaticAddress = errors .New ("no static address parameters found" )
30+ )
31+
2432// ManagerConfig holds the configuration for the address manager.
2533type ManagerConfig struct {
2634 // AddressClient is the client that communicates with the loop server
@@ -45,6 +53,10 @@ type ManagerConfig struct {
4553 // ChainNotifier is the chain notifier that is used to listen for new
4654 // blocks.
4755 ChainNotifier lndclient.ChainNotifierClient
56+
57+ // OnStaticAddressCreated is called after a new static address has been
58+ // stored locally and its tapscript has been imported into lnd.
59+ OnStaticAddressCreated func (context.Context ) error
4860}
4961
5062// Manager manages the address state machines.
@@ -199,27 +211,139 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
199211 return nil , 0 , err
200212 }
201213
214+ err = m .importAddressTapscript (ctx , staticAddress )
215+ if err != nil {
216+ return nil , 0 , err
217+ }
218+
219+ address , err := m .GetTaprootAddress (
220+ clientPubKey .PubKey , serverPubKey , int64 (serverParams .Expiry ),
221+ )
222+ if err != nil {
223+ return nil , 0 , err
224+ }
225+
226+ if m .cfg .OnStaticAddressCreated != nil {
227+ err = m .cfg .OnStaticAddressCreated (ctx )
228+ if err != nil {
229+ return nil , 0 , err
230+ }
231+ }
232+
233+ return address , int64 (serverParams .Expiry ), nil
234+ }
235+
236+ // RestoreAddress recreates a static address record locally and makes sure the
237+ // corresponding tapscript is imported into lnd. If the same address already
238+ // exists locally, the call is idempotent.
239+ func (m * Manager ) RestoreAddress (ctx context.Context ,
240+ addrParams * Parameters ) (* btcutil.AddressTaproot , error ) {
241+
242+ if addrParams == nil {
243+ return nil , fmt .Errorf ("missing static address parameters" )
244+ }
245+
246+ staticAddress , err := script .NewStaticAddress (
247+ input .MuSig2Version100RC2 , int64 (addrParams .Expiry ),
248+ addrParams .ClientPubkey , addrParams .ServerPubkey ,
249+ )
250+ if err != nil {
251+ return nil , err
252+ }
253+
254+ pkScript , err := staticAddress .StaticAddressScript ()
255+ if err != nil {
256+ return nil , err
257+ }
258+
259+ if len (addrParams .PkScript ) != 0 &&
260+ ! bytes .Equal (addrParams .PkScript , pkScript ) {
261+
262+ return nil , fmt .Errorf ("static address pk script mismatch" )
263+ }
264+
265+ addrParams .PkScript = pkScript
266+ if addrParams .InitiationHeight <= 0 {
267+ addrParams .InitiationHeight = m .currentHeight .Load ()
268+ }
269+
270+ m .Lock ()
271+ existing , err := m .cfg .Store .GetAllStaticAddresses (ctx )
272+ if err != nil {
273+ m .Unlock ()
274+
275+ return nil , err
276+ }
277+ switch {
278+ case len (existing ) == 0 :
279+ err = m .cfg .Store .CreateStaticAddress (ctx , addrParams )
280+ if err != nil {
281+ m .Unlock ()
282+
283+ return nil , err
284+ }
285+
286+ case len (existing ) > 1 :
287+ m .Unlock ()
288+
289+ return nil , fmt .Errorf ("more than one static address found" )
290+
291+ case ! sameAddressParameters (existing [0 ], addrParams ):
292+ m .Unlock ()
293+
294+ return nil , fmt .Errorf ("existing static address differs from " +
295+ "backup" )
296+ }
297+ m .Unlock ()
298+
299+ err = m .importAddressTapscript (ctx , staticAddress )
300+ if err != nil {
301+ return nil , err
302+ }
303+
304+ return m .GetTaprootAddress (
305+ addrParams .ClientPubkey , addrParams .ServerPubkey ,
306+ int64 (addrParams .Expiry ),
307+ )
308+ }
309+
310+ func (m * Manager ) importAddressTapscript (ctx context.Context ,
311+ staticAddress * script.StaticAddress ) error {
312+
202313 // Import the static address tapscript into our lnd wallet, so we can
203314 // track unspent outputs of it.
204315 tapScript := input .TapscriptFullTree (
205316 staticAddress .InternalPubKey , * staticAddress .TimeoutLeaf ,
206317 )
207318 addr , err := m .cfg .WalletKit .ImportTaprootScript (ctx , tapScript )
208319 if err != nil {
209- return nil , 0 , err
320+ // Restoring into an lnd instance that already imported the script is
321+ // expected. Treat the duplicate import as success.
322+ if strings .Contains (err .Error (), "already exists" ) {
323+ log .Infof ("Static address tapscript already imported" )
324+ return nil
325+ }
326+
327+ return err
210328 }
211329
212330 log .Infof ("Imported static address taproot script to lnd wallet: %v" ,
213331 addr )
214332
215- address , err := m .GetTaprootAddress (
216- clientPubKey .PubKey , serverPubKey , int64 (serverParams .Expiry ),
217- )
218- if err != nil {
219- return nil , 0 , err
333+ return nil
334+ }
335+
336+ func sameAddressParameters (a , b * Parameters ) bool {
337+ if a == nil || b == nil {
338+ return false
220339 }
221340
222- return address , int64 (serverParams .Expiry ), nil
341+ return a .ClientPubkey .IsEqual (b .ClientPubkey ) &&
342+ a .ServerPubkey .IsEqual (b .ServerPubkey ) &&
343+ a .Expiry == b .Expiry &&
344+ bytes .Equal (a .PkScript , b .PkScript ) &&
345+ a .KeyLocator == b .KeyLocator &&
346+ a .ProtocolVersion == b .ProtocolVersion
223347}
224348
225349// GetTaprootAddress returns a taproot address for the given client and server
@@ -297,7 +421,7 @@ func (m *Manager) GetStaticAddressParameters(ctx context.Context) (*Parameters,
297421 }
298422
299423 if len (params ) == 0 {
300- return nil , fmt . Errorf ( "no static address parameters found" )
424+ return nil , ErrNoStaticAddress
301425 }
302426
303427 return params [0 ], nil
0 commit comments