diff --git a/Cargo.toml b/Cargo.toml index a2c8387..54350db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" authors = ["John Winter Murphy "] description = "Open, self-hostable backend for the Smirk non-custodial multi-chain wallet" repository = "https://github.com/smirk-cash/smirk-backend-core" -# license: TBD — deciding MIT vs AGPL-3.0-or-later (see README). Left unset until chosen. +license = "MIT" # Not a library for crates.io; this is a deployable server. Guard against accidental publish. publish = false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c5cfd0d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Such Software LLC + +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. diff --git a/README.md b/README.md index b2246ca..3850a7a 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,123 @@ # smirk-backend-core -Open, self-hostable backend for the [Smirk](https://smirk.cash) non-custodial, -multi-chain wallet. It provides the chain-access and identity surface a Smirk -wallet needs — authentication, per-chain wallet access (Bitcoin, Litecoin, -Monero, Wownero, Grin), the Grin slatepack relay, and Nostr-based identity — -so anyone can run their own backend and a Smirk wallet can connect to it. +Open, self-hostable backend for the [Smirk](https://smirk.cash) non-custodial +multi-chain wallet. Rust + Axum + PostgreSQL. -> **Status: early, under active development.** Interfaces and the database -> schema may change before the first tagged release. Not yet recommended for -> production self-hosting. +It gives a wallet chain access (balances, history, UTXOs, spend inputs, fee +estimation, broadcast), Nostr-native identity, a fiat price feed, and an async +Grin slatepack relay — **without ever holding a spend key or seed**. Run your +own; the wallet is backend-agnostic. -## What it is +> **Status:** v0.3.0 — feature-complete and security-reviewed, but young. The +> schema/API may still evolve. Run your own chain backends where you can, review +> the code, and treat early deployments accordingly. -- **Non-custodial.** The server never holds spend keys or seeds. It brokers - access to blockchain data and relays wallet-to-wallet messages. -- **Open core, feature-flagged.** Every feature ships in this codebase and is - toggled per deployment; the wallet reads `GET /api/v1/capabilities` and - adapts to what a given backend offers. -- **Identity via "Sign in with Smirk".** Authentication uses a seed-derived - Nostr key (NIP-98); the server stores only public keys. -- **Privacy first.** No PII is required to use it; operator and user privacy - are explicit design goals. +## Non-custodial by construction -## Architecture +The server stores **public addresses, public keys, and (for Monero/Wownero) +incoming view keys only** — never a spend key, never a seed. View credentials are +**forwarded per request** to the chain backends that scan with them and are not +persisted here. The wallet signs and broadcasts locally; the backend reads chains +and relays bytes. -Rust + [Axum](https://github.com/tokio-rs/axum), PostgreSQL via -[sqlx](https://github.com/launchbadge/sqlx). The public HTTP contract is -generated from the handlers with [utoipa](https://github.com/juhaku/utoipa) -(OpenAPI), which in turn drives the wallet's generated TypeScript client — -one source of truth, checked for drift in CI. +## Chains -## Quickstart +| Chain | Source | Notes | +|-------|--------|-------| +| Bitcoin, Litecoin | Electrum / Fulcrum | reads, fee estimation, broadcast | +| Monero, Wownero | light-wallet-server (LWS) | stateless view-key forwarding | +| Grin | grin-wallet (view-only) + node | `rewind_hash` scan, broadcast, slatepack relay | + +Each chain is independently feature-flagged; a chain whose source isn't configured +is reported `enabled: false` by `/capabilities` rather than failing at call time. + +## API + +The HTTP contract is generated from the handlers and committed to +[`openapi.json`](openapi.json) — the single source of truth for the API and the +wallet's generated client. A CI gate fails the build on any drift. + +Public surface (`/api/v1`): + +- **Identity & auth** — NIP-98 (Nostr) sign-in + link, wallet-signature auth, + audience-separated JWT sessions, NIP-05 directory (`/.well-known/nostr.json`). +- **Chain access** — per-chain balance / history / UTXOs / spend inputs / fee / + broadcast. +- **Grin relay** — non-custodial store-and-forward mailbox for interactive Grin + transfers (feature-flagged). +- **Capabilities** (`/capabilities`) — what this instance enables, so the wallet + adapts per-instance. +- **Prices** (`/prices`) — cached fiat feed, per-feed operator control. +- **Self-service erasure** (`/account/erasure`, `/account/export`) — action-bound, + two-phase delete-my-data + export. +- **Health** (`/health`) and an optional, default-off public landing (`/`). + +## Operator surface + +Operator/admin functions live on a **separate loopback listener** (front it with +Tor or an SSH tunnel) — confidentiality by socket, never merged into the public +router or OpenAPI. + +- **Sign-in-with-Smirk admin auth** — a NIP-98 *signed action* over a single-use + nonce (AUTHN) + a MAC-protected key allowlist (AUTHZ), composed so a route can't + run without both. +- **First-run bootstrap** — an explicit, MAC-protected latch (no trust-on-first- + use); a live deployment adopts as already-bootstrapped; tampering fails closed. +- **`smirk-admin` CLI** — break-glass key management, `create-admin-wallet` + (zeroized), `doctor`. Talks to Postgres directly, bypassing the network plane. +- **Tamper-evident audit** — privileged actions are written to a hash-chained + audit trail, fail-closed (the state change rolls back if the audit write fails). + +## Running it + +Requires Rust (stable) and PostgreSQL. ```sh -cp .env.example .env # then fill in DATABASE_URL, JWT_SECRET, node URLs -cargo run # starts the API server +cp .env.example .env # then edit: DATABASE_URL + the required secrets +createdb smirk_backend_core # or point DATABASE_URL at an existing database +cargo run --release # migrations run automatically on startup ``` -A full self-hosting guide (node requirements, reverse proxy, Tor admin onion, -backups) will live in `docs/` as it stabilises. +The server **fails closed**: it refuses to start on a missing/weak secret or an +inconsistent feature configuration rather than booting with a control silently +defeated. Generate secrets with `openssl rand -hex 32`. See +[`.env.example`](.env.example) for the full, documented configuration surface. -## Security +First admin (headless — the supported bootstrap): -Please report vulnerabilities privately — see [SECURITY.md](SECURITY.md). -Do not open public issues for security reports. +```sh +smirk-admin create-admin-wallet --out admin.secret # generates a key, registers the pubkey +# import admin.secret into your NIP-98 signer; it activates on first admin login +``` + +## Security posture + +- **Fail-closed configuration** — the only place that reads the environment; + validates secrets (length + placeholder checks) and feature consistency at boot. +- **Identity at rest is peppered** — `pubkey_hash` / `seed_fingerprint` are stored + as HMACs, so the database is not a seed-existence oracle or a cross-instance + linker. +- **Strict JWTs** — HS256 pinned, zero leeway, audience-separated; admin tokens + are cryptographically distinct from user tokens. +- **Bounded, hardened upstream I/O** — TLS with hostname verification, streaming + size caps, and per-request timeouts on every external call. +- **Adversarially reviewed** — every security-critical subsystem was built clean + (never ported) and put through a multi-agent adversarial review before landing. + +Report vulnerabilities privately — see [SECURITY.md](SECURITY.md). Please do not +open public issues for security reports. + +## Testing + +```sh +cargo test # unit + doc tests (no database needed) +TEST_DATABASE_URL=postgres://… cargo test --tests # + L1 integration against Postgres +``` + +Integration tests self-skip when `TEST_DATABASE_URL` is unset. They use only +deterministic, non-sensitive test secrets and ephemeral identities — never a +funded or otherwise sensitive wallet seed. ## License -To be finalized (MIT vs AGPL-3.0-or-later). Until a `LICENSE` file is added, -all rights are reserved. +[MIT](LICENSE) © Such Software LLC — run it, embed it, modify it. Built for interop.