Add NRC (Nostr Relay Connect) protocol and web UI (v0.48.9)
Some checks failed
Go / build-and-release (push) Has been cancelled

- Implement NIP-NRC protocol for remote relay access through public relay tunnel
- Add NRC bridge service with NIP-44 encrypted message tunneling
- Add NRC client library for applications
- Add session management with subscription tracking and expiry
- Add URI parsing for nostr+relayconnect:// scheme with secret and CAT auth
- Add NRC API endpoints for connection management (create/list/delete/get-uri)
- Add RelayConnectView.svelte component for managing NRC connections in web UI
- Add NRC database storage for connection secrets and labels
- Add NRC CLI commands (generate, list, revoke)
- Add support for Cashu Access Tokens (CAT) in NRC URIs
- Add ScopeNRC constant for Cashu token scope
- Add wasm build infrastructure and stub files

Files modified:
- app/config/config.go: NRC configuration options
- app/handle-nrc.go: New API handlers for NRC connections
- app/main.go: NRC bridge startup integration
- app/server.go: Register NRC API routes
- app/web/src/App.svelte: Add Relay Connect tab
- app/web/src/RelayConnectView.svelte: New NRC management component
- app/web/src/api.js: NRC API client functions
- main.go: NRC CLI command handlers
- pkg/bunker/acl_adapter.go: Add NRC scope mapping
- pkg/cashu/token/token.go: Add ScopeNRC constant
- pkg/database/nrc.go: NRC connection storage
- pkg/protocol/nrc/: New NRC protocol implementation
- docs/NIP-NRC.md: NIP specification document

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
woikos
2026-01-07 03:40:12 +01:00
parent 0dac41e35e
commit d41c332d06
31 changed files with 5982 additions and 16 deletions

View File

@@ -6,6 +6,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
@@ -17,6 +18,7 @@ import (
"git.mleku.dev/mleku/nostr/crypto/keys"
"next.orly.dev/pkg/database"
"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
"git.mleku.dev/mleku/nostr/encoders/hex"
"next.orly.dev/pkg/neo4j"
"next.orly.dev/pkg/policy"
"next.orly.dev/pkg/protocol/graph"
@@ -26,6 +28,7 @@ import (
"next.orly.dev/pkg/cashu/issuer"
"next.orly.dev/pkg/cashu/keyset"
"next.orly.dev/pkg/cashu/verifier"
"next.orly.dev/pkg/protocol/nrc"
cashuiface "next.orly.dev/pkg/interfaces/cashu"
"next.orly.dev/pkg/ratelimit"
"next.orly.dev/pkg/spider"
@@ -199,6 +202,81 @@ func Run(
}
}
// Initialize NRC (Nostr Relay Connect) bridge if enabled
nrcEnabled, nrcRendezvousURL, nrcAuthorizedKeys, nrcUseCashu, nrcSessionTimeout := cfg.GetNRCConfigValues()
if nrcEnabled && nrcRendezvousURL != "" {
// Get relay identity for signing NRC responses
relaySecretKey, err := db.GetOrCreateRelayIdentitySecret()
if err != nil {
log.E.F("failed to get relay identity for NRC bridge: %v", err)
} else {
// Create signer from secret key
relaySigner, sigErr := p8k.New()
if sigErr != nil {
log.E.F("failed to create signer for NRC bridge: %v", sigErr)
} else if sigErr = relaySigner.InitSec(relaySecretKey); sigErr != nil {
log.E.F("failed to init signer for NRC bridge: %v", sigErr)
} else {
// Parse authorized secrets (format: secret:name,secret:name,...)
authorizedSecrets := make(map[string]string)
for _, entry := range nrcAuthorizedKeys {
parts := strings.SplitN(entry, ":", 2)
if len(parts) >= 1 {
secretHex := parts[0]
name := ""
if len(parts) == 2 {
name = parts[1]
}
// Derive pubkey from secret
secretBytes, decErr := hex.Dec(secretHex)
if decErr != nil || len(secretBytes) != 32 {
log.W.F("NRC: skipping invalid secret key: %s", secretHex[:8])
continue
}
derivedSigner, signerErr := p8k.New()
if signerErr != nil {
log.W.F("NRC: failed to create signer: %v", signerErr)
continue
}
if signerErr = derivedSigner.InitSec(secretBytes); signerErr != nil {
log.W.F("NRC: failed to init signer: %v", signerErr)
continue
}
derivedPubkeyHex := string(hex.Enc(derivedSigner.Pub()))
authorizedSecrets[derivedPubkeyHex] = name
}
}
// Construct local relay URL
localRelayURL := fmt.Sprintf("ws://localhost:%d", cfg.Port)
// Create bridge config
bridgeConfig := &nrc.BridgeConfig{
RendezvousURL: nrcRendezvousURL,
LocalRelayURL: localRelayURL,
Signer: relaySigner,
AuthorizedSecrets: authorizedSecrets,
SessionTimeout: nrcSessionTimeout,
}
// Add Cashu verifier if enabled
if nrcUseCashu && l.CashuVerifier != nil {
bridgeConfig.CashuVerifier = l.CashuVerifier
}
// Create and start the bridge
l.nrcBridge = nrc.NewBridge(bridgeConfig)
if err := l.nrcBridge.Start(); err != nil {
log.E.F("failed to start NRC bridge: %v", err)
l.nrcBridge = nil
} else {
log.I.F("NRC bridge started (rendezvous: %s, authorized: %d, cashu: %v)",
nrcRendezvousURL, len(authorizedSecrets), nrcUseCashu && l.CashuVerifier != nil)
}
}
}
}
// Initialize spider manager based on mode (only for Badger backend)
if badgerDB, ok := db.(*database.D); ok && cfg.SpiderMode != "none" {
if l.spiderManager, err = spider.New(ctx, badgerDB, l.publishers, cfg.SpiderMode); chk.E(err) {
@@ -720,6 +798,12 @@ func Run(
log.I.F("bunker server stopped")
}
// Stop NRC bridge if running
if l.nrcBridge != nil {
l.nrcBridge.Stop()
log.I.F("NRC bridge stopped")
}
// Stop WireGuard server if running
if l.wireguardServer != nil {
l.wireguardServer.Stop()