staticaddr: support multiple receive and change addresses - #1218
Open
hieblmi wants to merge 20 commits into
Open
staticaddr: support multiple receive and change addresses#1218hieblmi wants to merge 20 commits into
hieblmi wants to merge 20 commits into
Conversation
Reserve separate key families for static receive and change addresses. This keeps derived keys out of the legacy static-address and HTLC key streams.
Associate every deposit with the static address parameters that created it. This lets restored deposits recover the correct script and signing keys instead of assuming the legacy root address.
Create receive and change addresses from locally derived client keys while reusing the server key and expiry from the legacy seed. Persist, import, and activate each script before returning it to callers. Rebuild the active address index on startup and serialize issuance without blocking address reads. Import only scripts missing from lnd, and accept duplicate-import errors only when they identify the expected Taproot output key.
Look up each newly discovered wallet UTXO by script and persist the matching active-address parameters on the deposit. Reject unknown scripts before allocating the timeout sweep address. Use the per-deposit parameters when constructing the FSM, sign descriptor, and unilateral expiry sweep so derived-address recovery uses its owning script and key.
Register timeout-sweep confirmations by destination script instead of the originally published txid. This lets recovery detect an RBF replacement after restart with a stale txid.
Construct each cooperative MuSig2 session from the address parameters stored on its deposit. This prepares loop-ins and withdrawals to sign inputs belonging to different derived static addresses. Clean up sessions created before a later setup failure. Reject duplicate deposit outpoints to avoid leaking signer state. Validate transaction inputs, session handles, and nonce counts before signing so malformed responses fail before any signer operation.
Map every selected outpoint to the static address descriptor that derived its deposit and include those descriptors in loop-in requests. This lets the server validate mixed-address inputs independently of request order.
Include the derivation key for every withdrawal input in the server request. This lets the server validate and sign withdrawals that combine deposits from multiple derived addresses.
Join restored deposits with their persisted static-address rows and hydrate pre-migration deposits from the legacy root address. Return complete ownership metadata from deposit reads and use each deposit's own expiry during loop-in selection.
Associate fractional loop-ins with their operation-specific static change address so recovery restores the descriptor needed to reconstruct signed transactions. Backfill legacy fractional swaps to the original address.
Create a fresh static address for fractional loop-in change and send its descriptor to the server. Reconstruct signed HTLCs with the persisted parameters and verify cooperative batch change by output script.
Multi-address loop-ins sign and construct transactions from the parameters attached to each deposit and their dedicated change address. The legacy root address fields therefore became write-only, but populating them could still abort signing, sweep handling, or recovery when the root lookup failed. Remove those fields and lookups, select the FSM from the protocol version persisted with the swap, and set that version before constructing new state machines. Keep the root-parameter lookup used by autoloop expiry calculation and add regression coverage for recovery and unsupported persisted versions.
hieblmi
force-pushed
the
multi-address-core
branch
from
August 28, 2026 15:01
1179815 to
eb2cb02
Compare
Create a fresh static address for partial-withdrawal change and identify it in the confirmed transaction through its active change-family script, without assuming output order or count. Record withdrawn and change amounts by script identity. Keep all withdrawal outputs in the PSBT without separate signing metadata while preserving full-withdrawal behavior.
Let loop static deposit create and fund a fresh receive address through lnd SendCoins. Validate funding arguments before address creation and require explicit confirmation unless --force is set, including for non-interactive and first-use deposits. Allow NewStaticAddress RPC callers to fund a requested existing static address by resolving it through the active script index. Expose the nested request through the client RPC, require swap:execute permission, and cover the CLI new-address and daemon existing-address funding paths. Regenerate RPC and CLI documentation.
Include the owning static address in every deposit RPC response and CLI listing. Users can distinguish deposits created by different receive and change addresses without reconstructing scripts externally. Calculate blocks until expiry from each deposit owner instead of the legacy root address, and reject deposits whose owning parameters are missing. Centralize deposit response conversion and update generated RPC artifacts, regression coverage, and command replay fixtures.
The CLI previously recognized an uninitialized static-address seed by searching arbitrary gRPC error text. Any wrapping or wording change could suppress the L402 backup warning before a user funded a newly derived address. Map ErrNoStaticAddress to codes.NotFound at the RPC boundary and classify that status in the CLI. Retain compatibility with older daemons only for an exact Unknown-status message, avoiding the broad substring match, and cover both sides with regression tests.
A static-address account can now receive deposits across multiple derived addresses, so the singular summary field can no longer describe the current receive address. Removing or repurposing field 1 would break existing clients. Keep the wire value as the legacy/root derivation address, formally deprecate it, document the expiry as the shared CSV delay, and direct CLI users to derive a fresh receive address. Rename the server locals to make the compatibility behavior explicit and regenerate protobuf and Swagger artifacts.
Cover per-deposit address ownership and operation-specific change outputs across the shared SQL persistence boundary. Reconstruct the deposit, loop-in, and withdrawal stores to verify ownership and change metadata survive restart.
Document fresh receive-address derivation, lazy seed initialization, funding-address lookup hardening, and the swap:execute permission required by address creation. Regenerate the CLI, gRPC, Swagger, and man-page documentation and add feature, breaking-change, and recovery release notes.
hieblmi
force-pushed
the
multi-address-core
branch
from
August 28, 2026 15:09
eb2cb02 to
7aa2e74
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is PR 1 of 3 in the Static Address multi-address stack.
Static Address previously treated one legacy/root address as the owner of every
deposit. This PR introduces fresh receive and operation-specific change
addresses while preserving the address parameters that own each deposit.
Loop-ins and withdrawals can consequently spend deposits received across
multiple derived addresses, with each input signed and proven using its actual
address parameters.
Key Changes
across restarts.
address.
loop-ins and partial withdrawals.
errors for the expected Taproot output key.
of reconstructing and scanning every persisted address.
paying the same destination script cannot finalize a deposit.
loop static depositsupport for creating and optionally funding a freshaddress through lnd
SendCoins.RPC and Compatibility Changes
NewStaticAddressnow derives a fresh receive address when no existing addressis supplied through
send_coins_request.addr. Callers must not assume repeatedrequests are idempotent or return the same address.
Because address creation mutates wallet and database state, the RPC permission
changes from
swap:readtoswap:execute. Operators using custom scopedmacaroons must rebake them accordingly.
StaticAddressSummaryResponse.static_addressremains populated with thelegacy/root address for wire compatibility, but is deprecated and must not be
treated as the current receive address. Call
NewStaticAddressto derive afresh address.
Database and Recovery
The database now records:
Migrations backfill existing records using the legacy/root address. Store
reconstruction restores deposit ownership and loop-in and withdrawal change
metadata after restart.
Stacked PRs
pull/1215)
validates and follows the transactions that actually replace multi-address
withdrawals.
persists the confirmed HTLC output so recovery can rebuild the exact
server-published transaction.
The address startup and lookup hardening previously isolated in
#1214 has been folded into
this PR, and #1214 is now closed.
Testing
go test ./...go vet ./...go test -race ./staticaddr/address ./loopdmake docs-checkgit diff --checkRelease Notes
Release notes document the new address behavior, RPC permission change,
deprecated summary field, migration compatibility, and address lookup
hardening.