Skip to content

wallet+db: add GetAccountSecret Store surface and keyvault routing#1272

Draft
yyforyongyu wants to merge 18 commits into
sql-walletfrom
impl-secret-store
Draft

wallet+db: add GetAccountSecret Store surface and keyvault routing#1272
yyforyongyu wants to merge 18 commits into
sql-walletfrom
impl-secret-store

Conversation

@yyforyongyu

Copy link
Copy Markdown
Collaborator

Adds the account-secret surface to the runtime db.Store: the GetAccountSecret
query, secret types, sqlite/pg implementations, and the store-contract method.
Also adds the legacy manager keyvault shim and routes wallet unlock/lock and
signer key derivation through the keyvault (store-backed derivation primitives,
with a legacy-miss fallback).

Split out of the secret Store PR for reviewability; content-preserving, no
commit changes. Stacked on impl-runtime-store-5.

@yyforyongyu yyforyongyu added this to the Introduce SQL store milestone Jun 17, 2026
@yyforyongyu yyforyongyu self-assigned this Jun 17, 2026
@coveralls

coveralls commented Jun 17, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29839394920

Warning

No base build found for commit 2ea76df on sql-wallet.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 57.0%

Details

  • Patch coverage: 417 uncovered changes across 22 files (1357 of 1774 lines covered, 76.49%).

Uncovered Changes

Top 10 Files by Coverage Impact Changed Covered %
wallet/signer.go 256 159 62.11%
waddrmgr/manager.go 265 211 79.62%
wallet/internal/sql/pg/sqlc/accounts.sql.go 41 0 0.0%
wallet/internal/sql/pg/sqlc/db.go 24 0 0.0%
wallet/internal/keyvault/vault_lifecycle.go 97 76 78.35%
wallet/internal/sql/sqlite/sqlc/accounts.sql.go 41 20 48.78%
wallet/internal/sql/sqlite/sqlc/db.go 24 3 12.5%
wallet/internal/db/pg/accountstore_getaccountsecret.go 142 124 87.32%
wallet/internal/db/sqlite/accountstore_getaccountsecret.go 142 124 87.32%
wallet/internal/sql/pg/sqlc/addresses.sql.go 17 0 0.0%
Total (33 files) 1774 1357 76.49%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 61061
Covered Lines: 34805
Line Coverage: 57.0%
Coverage Strength: 14293.49 hits per line

💛 - Coveralls

@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch from 449c243 to e919fcb Compare June 17, 2026 12:22
@saubyk saubyk added this to lnd v0.22 Jun 17, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in lnd v0.22 Jun 17, 2026
@yyforyongyu
yyforyongyu marked this pull request as draft June 17, 2026 15:23
@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

cc @GustavoStingelin for taking over, I think we can open a new PR instead, as this PR is only a draft.

@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch from e919fcb to e8d5e1a Compare June 17, 2026 16:06
@yyforyongyu
yyforyongyu force-pushed the impl-secret-store branch 2 times, most recently from c70b257 to f572dca Compare June 17, 2026 16:25
@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch 2 times, most recently from b7b60d7 to a354b6c Compare June 17, 2026 16:42
@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch from a354b6c to 0908fc0 Compare June 17, 2026 17:32
@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch from 0908fc0 to 91960b1 Compare June 17, 2026 19:05
@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch from 91960b1 to edba65b Compare June 17, 2026 19:17
@yyforyongyu
yyforyongyu force-pushed the impl-secret-store branch 2 times, most recently from 4b277a5 to de9ddb6 Compare June 18, 2026 06:15
@yyforyongyu
yyforyongyu force-pushed the impl-runtime-store-5 branch 8 times, most recently from a5924bd to edea01b Compare June 18, 2026 18:34
@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

No issues found by glm-5.2 on re-review of the updated PR. 💡

Comment thread wallet/internal/db/kvdb/vault.go
Comment thread wallet/internal/db/kvdb/vault.go Outdated
Comment thread wallet/controller.go
Comment thread wallet/signer.go
Comment thread wallet/signer.go Outdated
Comment thread wallet/internal/db/itest/accountstore_getaccountsecret_test.go
Comment thread wallet/internal/bwtest/keyvaultmock/vault.go
Add the GetAccountSecret query to the pg and sqlite account query sets
and regenerate the sqlc bindings. The query joins accounts to key_scopes
and left-joins account_secrets so callers can distinguish a watch-only
account (no secret row) from an absent account. Generated code is kept
in its own commit, separate from the hand-written Go that consumes it.
Add the AccountSecret result type and the GetAccountSecretQuery
selector, plus the GetAccountSecretQuery.Validate helper and the
ErrAccountSecretUnavailable sentinel. AccountSecret carries only
encrypted private-key material; decryption stays with the wallet key
vault. These types back the per-backend GetAccountSecret
implementations that follow.
Add the PostgreSQL Store.GetAccountSecret method backed by the generated
GetAccountSecret query. It validates the selector, maps the row to the
backend-independent AccountSecret, and reports a typed
ErrAccountNotFound when no account row matches. The method is not yet
part of the AccountStore interface; that wiring lands once every backend
implements it.
Add the SQLite Store.GetAccountSecret method backed by the generated
GetAccountSecret query, mirroring the PostgreSQL implementation:
validate the selector, map the row to AccountSecret, and surface
ErrAccountNotFound for a missing account. Interface wiring lands once
all backends implement it.
Add GetAccountSecret to the AccountStore interface now that pg and
sqlite implement it, and complete the remaining implementations: the
kvdb backend reports ErrAccountSecretUnavailable (legacy secrets stay
in waddrmgr), and the testify mock store gains the method. The runtime
cache exposes a pass-through GetAccountSecret seam, and an itest covers
derived, watch-only, and absent-account outcomes.
Add CreateWalletSecrets, the create-side companion to the vault's
decrypt/rotate lifecycle: derive a master key from the private
passphrase, generate fresh script and private crypto keys, and encrypt
them (plus the master HD key) into the db.WalletSecrets a new wallet
persists. This mirrors the genesis waddrmgr.Create performs for the
legacy backend, so a SQL-backed wallet unlocks and derives the same keys
for a given seed. Watch-only wallets produce only the script key.
Add deriveStoredAccountChildKey, which decrypts an account encrypted
private key through the key vault and derives the branch/index leaf key
while zeroing intermediate material. Add the waddrmgr class-error helper
and signer error sentinels used by the following routing commit.
Add derivePathPrivKey and resolveDerivedPrivKeyFromStore, and thread a
context through the signing paths (ECDH, SignDigest, ComputeRawSig,
DerivePrivKey and the output/imported-key helpers). When the legacy
waddrmgr lookup misses on a scope or account, resolve the account's
encrypted private key from the Store and derive the leaf key through the
key vault, with precise watch-only and not-in-store errors. Covers
SQL-only accounts that have no mirrored waddrmgr row.
Add the GetAccountSecretById and GetAddressSecretByScriptPubKey queries
to the pg and sqlite query sets and regenerate the sqlc bindings. The
source query and its generated output are kept together in this commit,
separate from the hand-written Go that consumes them, which follows.

GetAccountSecretById selects a single account's encrypted private-key
material by row id, and GetAddressSecretByScriptPubKey resolves an
address secret from its script pubkey for backends with no synthetic
per-address row identity.
Add Manager.ChangePassphrases and its staged transaction machinery.
Public and private passphrases rotate atomically. Database writes
precede an OnCommit swap; error and race paths preserve locked state and
clear staged secrets.

This is forced atomic: staging, persistence, and commit helpers form one
transaction primitive and cannot be split without incomplete semantics.
Focused tests land with the adjacent stored-secret test file next.
Expose encrypted account and managed-address secret material without
decrypting it. Account reads return public metadata and encrypted keys.
Address reads identify the crypto key protecting each script.

Add coverage for account and address reads, defensive copies, and the
atomic passphrase primitive introduced immediately before this commit.
Expand passphrase rotation to explicit public and private requests.
Preserve vault lock state while persisted secrets are re-encrypted.
Validate genesis inputs and carried HD root material before persistence.

Move the Vault test double into keyvaultmock. In-package keyvault tests
import the shared Store mock; keeping Vault there would make the mock
import keyvault and create a cycle.
Adapt the legacy address manager to keyvault.Vault with consistent
lock, unlock, encryption, and error semantics. Passphrase changes call
Manager.ChangePassphrases within one walletdb transaction, preserving
lock state and supporting public and private rotation requests.
Add the shared account-secret selector and validation, including durable
account row identity lookups. Implement the expanded contract in kvdb,
PostgreSQL, and SQLite with focused unit and integration coverage.

This is a forced-atomic adapter step: the shared AccountID selector and
all three backends must land together to preserve the Store contract.
Add shared address-secret selector validation and crypto-key metadata.
Implement address ID and script pubkey lookups for PostgreSQL and SQLite
with focused conversion, backend, and integration coverage. The existing
kvdb method remains in place until its managed-address adapter follows.
Resolve imported addresses through their managed-address owner. Return
encrypted private key or script material without decrypting it. Preserve
the script crypto-key choice across transport, import, and read paths.

Add focused secret, copy, cancellation, and encryption tests.
Construct legacy wallets with the manager-backed vault. Route unlock,
lock, shutdown, and passphrase requests through that boundary. Remove
the direct DBUnlock helper and call keyVault.ChangePassphrase with the
full public and private request.
Resolve path-driven signing deterministically from account secrets in
Store and derive through keyvault. Address-driven signing uses durable
account identity. Imported keys and scripts use per-address secrets and
the reported crypto-key type. Thread identity through PSBT signing.

This is a forced-atomic wallet test migration. Shared signer helpers,
PSBT fixtures, and address-manager fixtures do not compile separately
from the production routing changes.
@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

No issues found by k3 🎉

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

No issues found by glm-5.2 on re-review of the updated PR. 🌙

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

No issues found by glm-5.2 on re-review. PR unchanged since last review. 💤

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

No issues found by gpt-5.5 🦉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants