mirror of
https://github.com/Such-Software/grin-lws.git
synced 2026-10-17 07:31:34 +02:00
Prepare for public release: generic wording, MIT LICENSE
Make the scaffold standalone and publish-ready: - Rewrite code + README references to the originating backend as a generic "wallet backend" so the service reads as a reusable, run-anywhere LWS. - Add the MIT LICENSE file (matches Cargo.toml) and a short provenance note. - Add repository/readme/keywords/categories metadata to Cargo.toml.
This commit is contained in:
@@ -5,6 +5,11 @@ edition = "2021"
|
||||
rust-version = "1.75"
|
||||
description = "Grin light-wallet-server: register a view credential, scan the chain in the background, store recognized outputs in a DB, and serve balance/outputs/broadcast/height."
|
||||
license = "MIT"
|
||||
repository = "https://github.com/Such-Software/grin-lws"
|
||||
readme = "README.md"
|
||||
keywords = ["grin", "light-wallet-server", "lws", "mimblewimble", "wallet"]
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
# crates.io publish is gated until the rangeproof-rewind crypto lands (scaffold).
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Such Software
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -5,9 +5,9 @@ view credential, run a **background scanner** over the Grin chain, **store**
|
||||
recognized outputs (with recovered derivation paths) in a **database**, and
|
||||
serve balance / unspent-outputs / broadcast / height fast from that store.
|
||||
|
||||
It is the Grin analogue of monero-lws, and lets any backend proxy to it exactly
|
||||
the way `smirk-backend-core`'s `xmr_wow.rs` proxies to the Monero LWS via
|
||||
`infra/lws/client.rs`.
|
||||
It is the Grin analogue of monero-lws: a wallet backend proxies to it exactly the
|
||||
way a backend proxies to the Monero LWS — forward a view credential, serve the
|
||||
stored results back to the wallet.
|
||||
|
||||
**Status: SCAFFOLD.** The HTTP surface, DTOs, config, database layer, and the
|
||||
scanner loop *structure* are real and build. The two chain-crypto seams — block
|
||||
@@ -20,12 +20,12 @@ milestone (see [Build order](#build-order)).
|
||||
|
||||
## Why this exists (and why it needs a DB)
|
||||
|
||||
`smirk-backend-core`'s current `POST /wallet/grin/scan` is an **on-demand,
|
||||
stateless proxy**: each request forwards a `rewind_hash` to grin-wallet's
|
||||
`scan_rewind_hash`, which rescans the chain and returns matches, **storing
|
||||
nothing**. That works for Grin's small UTXO set, but it is *not* a
|
||||
light-wallet-server — every read re-pays the scan cost, and it has no output
|
||||
store to recover derivation paths from.
|
||||
A common wallet-backend approach is an **on-demand, stateless** grin scan: each
|
||||
request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
|
||||
rescans the chain and returns matches, **storing nothing**. That works for
|
||||
Grin's small UTXO set, but it is *not* a light-wallet-server — every read
|
||||
re-pays the scan cost, and it has no output store to recover derivation paths
|
||||
from.
|
||||
|
||||
A real LWS — like monero-lws — keeps a **per-account output store** maintained by
|
||||
a **background scanner**, so client reads are O(rows) not O(chain), and it can
|
||||
@@ -145,33 +145,32 @@ chain_cursor(id=1 PK, height, block_hash) -- reorg-safe resume
|
||||
|
||||
---
|
||||
|
||||
## How `smirk-backend-core` proxies to it
|
||||
## How a wallet backend proxies to it
|
||||
|
||||
Identical wiring to the XMR/WOW LWS (`infra/lws/client.rs`):
|
||||
Identical wiring to a Monero LWS client — forward the view credential, serve the
|
||||
stored results:
|
||||
|
||||
```
|
||||
wallet ──JWT──▶ smirk-backend-core ──private──▶ grin-lws ──▶ grin node
|
||||
/api/v1/wallet/grin/scan /get_unspent_outs (Foreign API v2)
|
||||
wallet ──JWT──▶ wallet backend ──private──▶ grin-lws ──▶ grin node
|
||||
/wallet/grin/scan /get_unspent_outs (Foreign API v2)
|
||||
```
|
||||
|
||||
1. Add `GrinLwsConfig { lws_url, admin_url?, admin_key? }` in `config.rs`, next
|
||||
to `LwsConfig`.
|
||||
2. Add a `GrinLwsClient` in `infra/` mirroring `infra/lws/client.rs` (shared
|
||||
`reqwest::Client`, timeouts, size-capped hostile-response reads, `Secret`-
|
||||
wrapped admin key).
|
||||
3. Behind a `features.chains.grin_lws` flag, `POST /wallet/grin/scan` forwards
|
||||
the `rewind_hash` to grin-lws `/get_unspent_outs` (returning outputs **with
|
||||
paths**) instead of driving grin-wallet `scan_rewind_hash` on-demand.
|
||||
1. Add a grin-lws client config (`lws_url`, optional `admin_url` + `admin_key`)
|
||||
alongside your existing Monero-LWS config.
|
||||
2. Add an HTTP client mirroring your Monero-LWS client (shared `reqwest::Client`,
|
||||
timeouts, size-capped hostile-response reads, a `Secret`-wrapped admin key).
|
||||
3. Behind a feature flag, have your grin scan endpoint forward the `rewind_hash`
|
||||
to grin-lws `/get_unspent_outs` (returning outputs **with paths**) instead of
|
||||
driving grin-wallet `scan_rewind_hash` on-demand.
|
||||
4. Register at balance-fetch time by forwarding `rewind_hash` to grin-lws
|
||||
`/register` (idempotent), exactly as `fetchLwsBalance` awaits `registerLws`
|
||||
for XMR/WOW.
|
||||
`/register` (idempotent), exactly as you await LWS registration for Monero.
|
||||
|
||||
**Non-custodial guarantee preserved end to end:** the backend forwards only the
|
||||
`rewind_hash` and stores nothing; grin-lws holds the `rewind_hash` for scanning
|
||||
just as monero-lws holds the view key. No spend key ever leaves the wallet.
|
||||
|
||||
When grin-lws ships, the client can **drop** its `grin_identify_output` helper —
|
||||
`/get_unspent_outs` returns paths directly.
|
||||
Once grin-lws is wired in, the wallet client can **drop** any client-side
|
||||
output-identify helper — `/get_unspent_outs` returns paths directly.
|
||||
|
||||
---
|
||||
|
||||
@@ -228,3 +227,12 @@ Run as a private service alongside a grin node and its database. Bind to
|
||||
loopback / a private network; expose it only to the proxying backend. Add a
|
||||
readiness probe on `/ready`. Everything is env-configured — no code changes to
|
||||
deploy.
|
||||
|
||||
---
|
||||
|
||||
## License & provenance
|
||||
|
||||
MIT — see [`LICENSE`](LICENSE).
|
||||
|
||||
grin-lws was extracted from the [Smirk](https://smirk.cash) wallet's backend and
|
||||
generalized into a standalone service that any wallet backend can run.
|
||||
|
||||
+6
-5
@@ -1,10 +1,11 @@
|
||||
//! HTTP surface — mirrors the monero-lws user API so smirk-backend-core can
|
||||
//! proxy to it exactly as it proxies the XMR/WOW LWS (see `infra/lws/client.rs`).
|
||||
//! HTTP surface — mirrors the monero-lws user API so a wallet backend can proxy
|
||||
//! to it exactly as it proxies a Monero LWS.
|
||||
//!
|
||||
//! AUTH: the user API is unauthenticated at THIS layer — grin-lws binds to
|
||||
//! loopback / a private network and trusts smirk-backend-core (which does the
|
||||
//! JWT auth) as its only caller. The admin API (`/list_accounts`, `/rescan`) is
|
||||
//! gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled when unset.
|
||||
//! loopback / a private network and trusts the proxying wallet backend (which
|
||||
//! does the JWT auth) as its only caller. The admin API (`/list_accounts`,
|
||||
//! `/rescan`) is gated by a shared bearer (`GRINLWS_ADMIN_KEY`) and disabled
|
||||
//! when unset.
|
||||
//!
|
||||
//! The `rewind_hash` in every user request is a VIEW credential (public-key
|
||||
//! derived — it cannot spend). Its request DTOs omit `Debug` so it is never
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
//! Error type mapped to HTTP responses.
|
||||
//!
|
||||
//! Hostile-upstream hygiene (carried over from smirk-backend-core's LWS/grin
|
||||
//! clients): a node/DB error NEVER interpolates an untrusted upstream response
|
||||
//! body into the client-facing message.
|
||||
//! Hostile-upstream hygiene: a node/DB error NEVER interpolates an untrusted
|
||||
//! upstream response body into the client-facing message.
|
||||
|
||||
use axum::{http::StatusCode, response::IntoResponse, Json};
|
||||
|
||||
|
||||
+2
-3
@@ -4,9 +4,8 @@
|
||||
//! block outputs (commit + rangeproof), and relay a finalized transaction. This
|
||||
//! is the ONLY component that reaches the chain; everything else reads the DB.
|
||||
//!
|
||||
//! Hostile-upstream hygiene (carried from smirk-backend-core's node client):
|
||||
//! per-request + connect timeouts, a streaming body-size cap, and errors that
|
||||
//! never interpolate an untrusted node body.
|
||||
//! Hostile-upstream hygiene: per-request + connect timeouts, a streaming
|
||||
//! body-size cap, and errors that never interpolate an untrusted node body.
|
||||
//!
|
||||
//! REWIND (the hard, deferred part): recognizing which of a block's outputs
|
||||
//! belong to a registered account means rewinding each output's Bulletproof
|
||||
|
||||
+17
-18
@@ -10,25 +10,24 @@
|
||||
//!
|
||||
//! WHY A DB
|
||||
//! --------
|
||||
//! smirk-backend-core's current `POST /wallet/grin/scan` is an ON-DEMAND,
|
||||
//! STATELESS proxy: each request forwards a `rewind_hash` to grin-wallet's
|
||||
//! `scan_rewind_hash`, which rescans and returns matches, storing NOTHING. That
|
||||
//! is fine for grin's small UTXO set but it is NOT a light-wallet-server. A real
|
||||
//! LWS — like monero-lws — keeps a per-account output store maintained by a
|
||||
//! background scanner, so reads are O(rows) not O(chain). Hence this service
|
||||
//! needs a database (the 3 tables in `migrations/`).
|
||||
//! A typical wallet backend exposes an ON-DEMAND, STATELESS grin scan: each
|
||||
//! request forwards a `rewind_hash` to grin-wallet's `scan_rewind_hash`, which
|
||||
//! rescans and returns matches, storing NOTHING. That is fine for grin's small
|
||||
//! UTXO set but it is NOT a light-wallet-server. A real LWS — like monero-lws —
|
||||
//! keeps a per-account output store maintained by a background scanner, so reads
|
||||
//! are O(rows) not O(chain). Hence this service needs a database (the 3 tables in
|
||||
//! `migrations/`).
|
||||
//!
|
||||
//! HOW smirk-backend-core PROXIES TO IT
|
||||
//! ------------------------------------
|
||||
//! Identical wiring to the XMR/WOW LWS (`infra/lws/client.rs`): the backend adds
|
||||
//! a `GrinLwsConfig { lws_url, admin_url?, admin_key? }` and a `GrinLwsClient`;
|
||||
//! when the `grin_lws` feature is on, `POST /wallet/grin/scan` forwards the
|
||||
//! `rewind_hash` to this service's `/get_unspent_outs` (and registers via
|
||||
//! `/register` at balance-fetch time, idempotently, exactly as `fetchLwsBalance`
|
||||
//! awaits `registerLws` for XMR/WOW). Non-custodial guarantee preserved: the
|
||||
//! backend forwards only the `rewind_hash` and stores nothing; grin-lws holds
|
||||
//! the `rewind_hash` for scanning just as monero-lws holds the view key. NEVER a
|
||||
//! spend key.
|
||||
//! HOW A WALLET BACKEND PROXIES TO IT
|
||||
//! ----------------------------------
|
||||
//! Identical wiring to a Monero LWS client: the backend adds a grin-lws client
|
||||
//! (`lws_url`, optional `admin_url` + `admin_key`); behind a feature flag, its
|
||||
//! grin scan endpoint forwards the `rewind_hash` to this service's
|
||||
//! `/get_unspent_outs` (and registers via `/register` at balance-fetch time,
|
||||
//! idempotently, exactly as it awaits LWS registration for Monero). Non-custodial
|
||||
//! guarantee preserved: the backend forwards only the `rewind_hash` and stores
|
||||
//! nothing; grin-lws holds the `rewind_hash` for scanning just as monero-lws
|
||||
//! holds the view key. NEVER a spend key.
|
||||
//!
|
||||
//! STATUS: SCAFFOLD. Routes, DTOs, config, DB layer, and the scanner loop
|
||||
//! structure are real; the two chain-crypto seams — block parsing
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
//! A tiny redacting wrapper so secrets (DB URL, node auth, admin key) never leak
|
||||
//! into `Debug` output or tracing spans. Mirrors smirk-backend-core's `Secret`.
|
||||
//! into `Debug` output or tracing spans.
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Secret(String);
|
||||
|
||||
Reference in New Issue
Block a user