feat(auth): npub-native registration (POST /auth/nostr/register) - Nostr-native identity

Make npub the identity anchor: a wallet can register from its seed-derived Nostr
key alone, no BTC signature. Breaks the link chicken-and-egg (nostr_login is
create-never; nostr_link needs a prior BTC-authed JWT). Additive - /auth/extension
(BTC) and the link flow keep working for legacy backends during the transition.

- get_or_create_user_by_nostr_pubkey (db twin of the BTC path): keys/inserts on
  nostr_pubkey (raw, never peppered), pubkey_hash NULL. THE load-bearing dedup:
  looks up by seed_fingerprint and MERGES the npub onto an existing (BTC-anchored
  or link-less) row rather than splitting one wallet into two identities; a
  different npub already on that row fails closed (409). backfill_optionals shared
  helper.
- POST /auth/nostr/register + GET /auth/nostr/register-challenge: verify a NIP-98
  signed-action over a subject-less single-use nonce (replay-safe), then re-run
  the abuse gates keyed on the npub (a create path must not bypass PoW/invite/
  payment); pay-to-register invoices still bind to the BTC key carried in keys[].
  Empty-body descriptor contract (mirrors /auth/nostr/link); binding the key list
  into the proof is a documented hardening follow-up.
- Housekeeping: get_user_count counts npub-only users; record_login_event labels
  the nostr path.

3 integration KATs (create-by-npub + idempotent, replay rejected, seed_fingerprint
MERGE dedup) pass against a real DB; 211 lib tests pass; openapi regenerated.
This commit is contained in:
jwinterm
2026-07-05 08:02:47 -04:00
parent 6f0fca532d
commit c49872b62b
5 changed files with 669 additions and 5 deletions
+3
View File
@@ -27,6 +27,8 @@ use utoipa::OpenApi;
crate::api::auth::logout,
crate::api::auth::get_me,
crate::api::auth::nostr_login,
crate::api::auth::nostr_register_challenge,
crate::api::auth::nostr_register,
crate::api::auth::nostr_link_challenge,
crate::api::auth::nostr_link,
crate::api::website::website_challenge,
@@ -93,6 +95,7 @@ use utoipa::OpenApi;
crate::api::auth::NostrLinkChallengeResponse,
crate::api::auth::NostrLinkRequest,
crate::api::auth::NostrLinkResponse,
crate::api::auth::NostrRegisterRequest,
crate::api::website::WebsiteChallengeRequest,
crate::api::website::WebsiteChallengeResponse,
crate::api::website::AssetSignature,