Skip to content

Commit 64bbfc7

Browse files
committed
recovery: add backup and restore package
Introduce a dedicated recovery package for encrypted local backups of static-address state and raw l402 token files. The package owns the backup file format, seed-derived encryption, static-address key re-derivation with gap fallback, token file restore, and best-effort deposit reconciliation orchestration. It also includes package-level documentation and focused tests for the file helpers.
1 parent 727cbe2 commit 64bbfc7

3 files changed

Lines changed: 1110 additions & 0 deletions

File tree

recovery/README.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Recovery Package
2+
3+
This package implements phase-1 local recovery for Loop static addresses and
4+
L402 authentication state.
5+
6+
## Goal
7+
8+
The recovery flow is meant to let a fresh or repaired Loop instance continue
9+
using the same static address and the same L402-backed server identity after a
10+
local crash, disk loss, or Loop data-directory replacement.
11+
12+
The package also keeps older generations of recoverable state around. That
13+
matters when a user first creates one paid-L402/static-address pair, later
14+
loses the local Loop state, and then creates a different paid-L402/static-
15+
address pair. In that case the older deposits are still recoverable as long as
16+
the older timestamped backup file is kept.
17+
18+
The recovery data is intentionally local:
19+
20+
- It is written into the active Loop network data directory.
21+
- It contains only the minimum local state needed to rebuild client-side state.
22+
- It is encrypted with a key derived from the same lnd seed material that backs
23+
static-address keys.
24+
25+
## What Is Backed Up
26+
27+
Each encrypted backup stores:
28+
29+
- A backup format version.
30+
- The active Loop network.
31+
- Static-address protocol version.
32+
- Static-address server pubkey.
33+
- Static-address expiry.
34+
- The exact client key locator when available.
35+
- The static-address `pkScript`.
36+
- The derived taproot address string as an additional recovery hint.
37+
- The address initiation height.
38+
- The paid local `l402.token` file.
39+
40+
The paid token file is preserved as a raw blob instead of decomposed token
41+
fields so the restore path remains compatible with Aperture's current file-store
42+
format without requiring a public token constructor.
43+
44+
A given backup can contain:
45+
46+
- Only a paid L402 token.
47+
- A paid L402 token together with a static address.
48+
49+
Deposit state itself is not serialized into the backup file. Deposits are
50+
rediscovered on restore through the normal reconciliation path.
51+
52+
## File Location
53+
54+
This package keeps two backup forms in the active Loop network data directory:
55+
56+
- A canonical latest snapshot:
57+
`<loop-data-dir>/L402_backup.enc`
58+
- Immutable timestamped snapshots:
59+
`<loop-data-dir>/L402_backup_<timestamp>.enc`
60+
or `<loop-data-dir>/L402_backup_<timestamp>_<n>.enc`
61+
62+
In the normal `loopd` layout this resolves inside the active network-specific
63+
directory, for example:
64+
65+
`~/.loop/mainnet/L402_backup.enc`
66+
67+
and
68+
69+
`~/.loop/mainnet/L402_backup_20260414T093001.000000123Z.enc`
70+
71+
The canonical `L402_backup.enc` file is always updated to the latest snapshot
72+
for compatibility and convenience. The timestamped files are the archival
73+
history that lets the user recover older generations.
74+
75+
## Encryption Model
76+
77+
The file is encrypted with `secretbox` using a symmetric key derived from lnd
78+
via `Signer.DeriveSharedKey`.
79+
80+
The derivation uses:
81+
82+
- A fixed NUMS public key.
83+
- The static-address key family.
84+
- Key index `0`.
85+
86+
This gives the backup a deterministic local encryption key tied to the lnd seed
87+
without requiring an interactive password in phase 1.
88+
89+
## Backup Creation Flow
90+
91+
On startup, `loopd` asks this package to backfill the latest snapshot if any
92+
recoverable state exists.
93+
94+
The backup write is a no-op when:
95+
96+
- No static address exists locally.
97+
- No paid `l402.token` file exists locally.
98+
99+
If either state source exists, the package:
100+
101+
1. Collects the local static-address parameters.
102+
2. Reads the paid `l402.token` file from the Loop data dir.
103+
3. Serializes the payload as JSON.
104+
4. Encrypts it.
105+
5. If the recoverable state changed, atomically writes a new timestamped backup.
106+
6. Updates `L402_backup.enc` to the same latest snapshot.
107+
108+
If the recoverable state did not change, no new timestamped archive is created.
109+
This avoids producing duplicate backups on every startup.
110+
111+
The daemon also triggers backup refreshes when:
112+
113+
- A usable paid L402 token has just been fetched and stored locally.
114+
- A new static address has just been created, stored, and imported into lnd.
115+
116+
Pending or otherwise intermediate L402 token state is intentionally not backed
117+
up.
118+
119+
## Restore Flow
120+
121+
`loop recover --backup_file <path>` eventually calls into this package from
122+
inside `loopd`.
123+
124+
If the backup path is omitted, the package uses the timestamped backup with the
125+
latest filename timestamp, falling back to `L402_backup.enc` for legacy or
126+
backfill-only states.
127+
128+
Restore performs the following steps:
129+
130+
1. Read and decrypt the backup file.
131+
2. Validate backup version and network.
132+
3. If static-address metadata is present, reconstruct the static-address client
133+
pubkey:
134+
- First try the exact backed-up key locator.
135+
- If the locator is missing or unusable, scan backward with a gap of 20 in
136+
the static-address key family.
137+
4. If static-address metadata is present, re-create the local static-address
138+
record.
139+
5. If static-address metadata is present, re-import the static-address
140+
tapscript into lnd.
141+
6. If a paid token is present, restore the paid `l402.token` file into the
142+
local token store directory.
143+
7. If static-address metadata is present, trigger best-effort deposit
144+
reconciliation.
145+
146+
Restoring an older timestamped backup is expected to be done into a fresh Loop
147+
data directory because Loop still only supports a single local static address at
148+
a time. The same recommendation applies when the local directory already
149+
contains a different paid L402 token, because recovery is intended to recreate a
150+
specific prior generation of local state rather than merge multiple ones.
151+
152+
## Deposit Reconciliation
153+
154+
This package does not attempt to fully rebuild the complete historical deposit
155+
state from chain data alone.
156+
157+
Instead, after static-address restore it calls the deposit manager's normal
158+
reconciliation path and lets Loop discover currently visible deposits from lnd's
159+
wallet view.
160+
161+
This is intentionally best effort:
162+
163+
- It is enough to recover practical local usability.
164+
- It avoids duplicating the full deposit FSM logic in the recovery layer.
165+
- It keeps the restore path aligned with normal manager behavior.
166+
167+
## Package Boundaries
168+
169+
This package owns:
170+
171+
- Backup payload definition.
172+
- Encryption/decryption.
173+
- Reading/writing backup files.
174+
- Reading/writing paid L402 token files.
175+
- Static-address key re-derivation and restore orchestration.
176+
- Post-restore deposit reconciliation orchestration.
177+
178+
This package does not own:
179+
180+
- gRPC transport.
181+
- CLI command handling.
182+
- `loopd` startup wiring.
183+
184+
Those remain in `loopd/` and `cmd/loop/`.

0 commit comments

Comments
 (0)