Claude/scan screen from to edit x36qp0 - #19
Open
ponthief wants to merge 108 commits into
Open
Conversation
…e-sqfxw0 Add GET /bip353/available for live username availability checks
The WalletAccount model defaulted last_scan_height to 1 while the DB column (migrations.py) defaults to 0. Since create_silnt_wallet inserts the model, freshly-created wallets persisted 1, so the app reported "Scanned to: block 1" for a wallet that had never been scanned. Default to 0 (never scanned) to match the schema and the resume logic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
Both routes defaulted an absent `network` to "signet", so a mainnet client that omitted it was served signet's chain tip / min_scan_height / dust threshold. Reject a missing or empty network with 400 instead, so the mismatch fails loud rather than returning wrong-network data. Admin and fee endpoints keep their existing defaults. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…36qp0 Fix network scoping for chain tip/config and last_scan_height default
sp_contacts was keyed only by user_id, so a user with wallets on more than one network (e.g. a mainnet build reusing a signet test account) saw every network's contacts — mainnet listed signet recipients. Add a `network` column (migration m024, existing rows backfilled to signet since they predate mainnet), filter list/dedup/touch by it, and require an explicit `network` on GET/POST /contacts (400 when missing), matching the oracle/tip + config contract. The send path passes the wallet's network when bumping last_used_at. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…36qp0 Scope the SP contacts address book per network
Contacts were deduped only by SP address, so a user could save two contacts with the same name pointing at different addresses. Require the label to be unique per user+network (case-insensitive): reject a create or rename whose name is already used by a different contact, returning 400 with a clear message. The same address renaming itself is still fine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…36qp0 Enforce unique contact names within a user's address book
Add an optional `sp_address` to wallet creation. When the client derives the seed and keys on-device (see the Thrilla spKeys module), it sends only the public sp_address and the server stores it as-is — the server never sees the mnemonic or the private keys. The prefix is validated against the network. The legacy path (server generates, or imports+derives from a supplied mnemonic) is unchanged for older clients and the web app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…36qp0 Wallet create: accept client-derived sp_address (server never sees the seed)
The module imported Python's `random` (Mersenne Twister) but never used it — all EC math goes through coincurve/ecdsa. Dead imports of a non-crypto RNG in a key-handling file are a footgun; drop it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…36qp0 curve.py: remove unused non-crypto random import
BIP-352 reserves label m=0 for change; siLNt produced change at m=1 and
treated the unlabeled base address as "m=0". That's self-consistent within
siLNt (it scans m=1) but breaks the spec's cross-wallet guarantee: any other
BIP-352 wallet restoring the same seed scans m=0 for change and would miss
siLNt's change outputs.
Produce new change at the standard m=0, and keep scanning m=1 as a legacy
change index so existing wallets' historical change stays detectable (all
wallets are currently on Signet, so no mainnet migration is involved).
- scan.py: change label index 1 → 0; add BIP352_LEGACY_CHANGE_LABEL_INDICES
= [1]; scan set = {0 (change), 1 (legacy), 2, 3 (user labels)}.
- wallet.py: generate_labeled_sp_address now accepts m=0 (a real label tweak,
the change label — never handed out); change scriptPubKey derived at m=0.
- dust_check.py: classify both m=0 and legacy m=1 change as non-dust.
- views_api.py / crud.py: reserve both m=0 and m=1 from user label assignment
on both address-save paths; fix stale docs.
Verified the m=0 label tweak is byte-identical on the produce and scan sides,
so change created at m=0 is detected at m=0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…36qp0 BIP-352: use m=0 for the change label (was m=1)
The per-transaction receiver scan loop was `while True`, so a maliciously crafted transaction packing many P2TR outputs to one scan key forced O(N^2) work (each match rescans the remaining outputs) — minutes of CPU per block. BIP-352 v1.1.0 caps a recipient group at K_max = 2323 outputs (the most that fit in a 100,000-vByte standard tx); stop scanning there to bound the work to O(N*K_max). Honest transactions never reach the cap, so conforming receivers are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
On a rescan, insert_utxos_for_wallet upserts (ON CONFLICT DO UPDATE) and preserves an existing spent state, so re-detecting an already-known UTXO changes nothing visible — no new coin, balance unchanged. But the scan counted every detected output (total_found += len(result)), so the UI's Progress stat, completion message, and toast reported e.g. "1 UTXO found" after a large rescan that actually found nothing new. insert_utxos_for_wallet now returns the number of rows that were genuinely newly inserted (existence pre-check), and the scan loop counts only those. Re-detected/updated rows no longer inflate the found count. Fixes the false "found" count on both the web and mobile clients (both read this counter). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…x36qp0 Scanning: BIP-352 K_max cap + fix false "found" count on rescans
Lets a user opt a wallet into server-side background scanning so a returning
user isn't faced with a huge catch-up. This is the BIP-352 "Remote Scanner"
model: the client uploads ONLY the wallet's scan key (a detection capability
— it can find payments but never spend), encrypted at rest. The spend key is
never uploaded; the background scanner derives the spend PUBLIC key from the
wallet's sp_address.
- migrations: m025 background_scan table (encrypted scan key; row present =
opted in; disabling deletes the key).
- crud: enable/disable/status/get-secret/list helpers; deleting a wallet also
drops its stored key.
- scan.py: scan_wallet can take a spend PUBLIC key directly (no spend secret
needed); scan_block's unused spend_secret param removed.
- views_api: GET/PUT/DELETE /wallet/{id}/background-scan (PUT validates the
uploaded key's pubkey matches the wallet's B_scan); run_background_scans()
sweeps opted-in wallets to the tip, skipping caught-up or mid-scan wallets.
- __init__: run the sweep on a 30-min timer alongside the other ext tasks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…x36qp0 Opt-in server-side background scanning (Remote Scanner)
When the opt-in background scan discovers new UTXOs for a wallet, push a "Payment received" notification to the owner's registered devices. - migrations: m026 fcm_tokens (device token -> user). - crud: register/remove/list FCM tokens. - helpers/fcm.py: FCM HTTP v1 sender (service-account OAuth via google-auth), prunes tokens FCM reports as unregistered. No-op unless SILNT_FCM_CREDENTIALS points at a service-account JSON, so the app is unaffected when push isn't set. - views_api: POST/DELETE /api/v1/fcm/token; run_background_scans fires a push (best-effort) when scan_wallet reports newly-found UTXOs. - pyproject: add google-auth. Client registers its token after login (separate Thrilla change). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
A 32-byte scan key is 64 hex chars — exactly AES-block-aligned — so
AESCipher can leave a trailing padding block on the decrypted string,
making bytes.fromhex() fail ("non-hexadecimal number ... at position 64")
and every background scan for that wallet error out (the wallet then falls
behind). Extract the leading 64-hex on read, which also repairs already-
stored keys without the user re-enabling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…x36qp0 Push notifications for background-scan payments + fix stored scan-key parse
AESCipher can leave a residual padding block on the decrypted string when the plaintext was exactly block-aligned (a multiple of 16 bytes), which is what broke background-scan key parsing. Fix it once at the decrypt boundary instead of per-caller: strip a trailing run of N identical control bytes (0x01–0x10) equal to N — that's leftover padding, never data, since every value we encrypt is printable text. A cleanly-decrypted value never matches, so it's a no-op in the normal case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq
…x36qp0 Harden _pj_decrypt: strip residual PKCS7 padding centrally
…ayment alerts (#14) * Add FCM test-push endpoint for push diagnostics Add POST /api/v1/fcm/test which sends a diagnostic notification to the caller's registered devices and returns a structured report (credentials present, tokens registered, per-token send result). Lets a user verify the whole push pipeline without waiting for a real payment and the 30-min background sweep. Backed by a new send_fcm_report() helper mirroring send_fcm but returning counts and human-readable failure reasons; invalid tokens are still pruned. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * FCM: report the specific credentials failure, resolve path at call-time The test endpoint previously collapsed three distinct failures into one misleading 'no credentials' line. Split them: env var unset, path set but file missing/unreadable, google-auth not installed, or an unparseable service-account file — each now returns its own explanation. Also resolve SILNT_FCM_CREDENTIALS at call-time (with an LNbits-settings fallback) instead of caching it at import, so a restart reliably picks up a freshly-added .env value regardless of import order. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * FCM: resolve credentials path from LNbits .env file directly The test surfaced that some deployments read .env into pydantic settings without exporting it to os.environ, so os.environ.get() returned empty even though the var was in .env. Add a deployment-agnostic fallback that parses LNbits' .env directly (LNBITS_ENV_FILE, the startup dir, and the data-folder location), after checking the process env and settings object. The 'not found' diagnostic now lists every .env path it checked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * FCM: mint OAuth2 tokens with cryptography, drop google-auth dependency LNbits does not auto-install extension pyproject deps, so requiring google-auth meant a manual venv install. Replace it with a small service-account client that signs the JWT-bearer assertion using cryptography (already an LNbits dependency) and exchanges it at Google's token endpoint — same FCM HTTP v1 result, zero extra installs. Tokens are cached until ~60s before expiry. The load path now validates the file is a real service-account key and gives a clear error if google-services.json was supplied by mistake. Removed google-auth from pyproject dependencies. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * Route all SILNT_* env reads through a shared .env-aware resolver Some LNbits deployments load .env into pydantic settings without exporting it into os.environ, so os.environ.get() misses vars that are present in .env (this was why FCM credentials weren't seen). Extract the .env-fallback logic into helpers/appenv.silnt_env() and use it for SILNT_BITMAIL_DOMAIN and SILNT_DEVICE_COOKIE_DOMAIN as well, so every custom var resolves the same way: process env → LNbits .env file → default. FCM now shares this helper instead of its own private copy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * Notify on interactive-scan finds, not only the background sweep Opening the app triggers an interactive catch-up scan that almost always beats the 30-min background sweep to a new payment (and then advances last_scan_height so the sweep skips as already-caught-up). With the push wired only into the sweep, an app-open find never notified. Fire _notify_payment_found whenever any scan inserts new UTXOs. utxos_found counts only newly-inserted rows, so rescans of known blocks don't notify. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * Notify with amount received instead of wallet balance The payment notification showed the total balance, which is noise. Report the amount just received instead: insert_utxos_for_wallet now returns the summed sats of newly-inserted rows, scan_wallet threads it out as amount_found (and into scan progress), and _notify_payment_found renders 'Received N sats in <wallet>'. Falls back to a count if the amount is unavailable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * Make payment push generic to keep amount off Google/FCM FCM notification messages transit Google in plaintext, so putting the amount (and wallet title) in the push leaked payment metadata to Google. Send a generic 'You've received a new payment' with minimal data ({type: payment}), no amount/name/count. The exact amount is still shown in-app, composed locally from scan data and never sent via FCM. The server already sees amounts by design (background scanning); this specifically keeps them off Google's servers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq --------- Co-authored-by: Claude <noreply@anthropic.com>
ponthief
force-pushed
the
claude/scan-screen-from-to-edit-x36qp0
branch
from
August 9, 2026 05:52
6dd2799 to
355a76f
Compare
…#15) Poll each active network's chain tip every 60s and run the sweep the moment the tip advances, so a received payment is detected and pushed within ~a block rather than up to 30 minutes later. The tip poll is cheap — one request per network with opted-in wallets (new list_background_scan_networks query), not one per wallet. The 30-min interval is kept as a safety-net floor (newly opted-in wallets, missed tip updates, server restarts). Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq Co-authored-by: Claude <noreply@anthropic.com>
ponthief
force-pushed
the
claude/scan-screen-from-to-edit-x36qp0
branch
from
August 9, 2026 06:34
459805d to
6df321f
Compare
…s still scanned (#16) * Document + enforce the labeled-address cap ↔ scanned-index coupling MAX_ADDRESSES_PER_WALLET (2) and BIP352_LABELED_ADDRESS_INDICES ([2,3]) are implicitly coupled: the server assigns labeled indices lowest-free from 2 and the cap bounds the highest to cap+1, and every such index must be in the always-scanned set so a *deleted* labeled address stays detectable. Add cross-referencing comments in both spots and a fail-fast import-time assert so raising the cap without widening the scanned set (which would silently reintroduce the delete-then-miss-payment footgun) can't slip through. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq * Clamp labeled-address index server-side to the always-scanned range Close the client-supplied-index escape from the cap/scanned-index coupling: a client could POST an explicit label_index outside BIP352_LABELED_ADDRESS_INDICES (e.g. m=50), persisting a labeled address that would silently stop being detected if later deleted. - preview: honor an explicit index only if it's a real, free labeled slot; otherwise fall back to the next free in-range slot (server regenerates the matching address). - save: reject an out-of-range/reserved explicit index (save has no keys to regenerate, so the client's sp_address is bound to its index and can't be safely remapped); None or a taken slot still auto-picks the next free in-range slot, which the per-wallet cap guarantees exists. Normal preview→save flow (server-assigned m=2/m=3) is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BizgTtdeH233srEaDHytBq --------- Co-authored-by: Claude <noreply@anthropic.com>
ponthief
force-pushed
the
claude/scan-screen-from-to-edit-x36qp0
branch
from
August 9, 2026 09:19
b2399ca to
8aee1e5
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.
No description provided.