Add Cashu blind signature access tokens (NIP-XX draft)

Implements privacy-preserving bearer tokens for relay access control using
Cashu-style blind signatures. Tokens prove whitelist membership without
linking issuance to usage.

Features:
- BDHKE crypto primitives (HashToCurve, Blind, Sign, Unblind, Verify)
- Keyset management with weekly rotation
- Token format with kind permissions and scope isolation
- Generic issuer/verifier with pluggable authorization
- HTTP endpoints: POST /cashu/mint, GET /cashu/keysets, GET /cashu/info
- ACL adapter bridging ORLY's access control to Cashu AuthzChecker
- Stateless revocation via ACL re-check on each token use
- Two-token rotation for seamless renewal (max 2 weeks after blacklist)

Configuration:
- ORLY_CASHU_ENABLED: Enable Cashu tokens
- ORLY_CASHU_TOKEN_TTL: Token validity (default: 1 week)
- ORLY_CASHU_SCOPES: Allowed scopes (relay, nip46, blossom, api)
- ORLY_CASHU_REAUTHORIZE: Re-check ACL on each verification

Files:
- pkg/cashu/bdhke/: Core blind signature cryptography
- pkg/cashu/keyset/: Keyset management and rotation
- pkg/cashu/token/: Token format with kind permissions
- pkg/cashu/issuer/: Token issuance with authorization
- pkg/cashu/verifier/: Token verification with middleware
- pkg/interfaces/cashu/: AuthzChecker, KeysetStore interfaces
- pkg/bunker/acl_adapter.go: ORLY ACL integration
- app/handle-cashu.go: HTTP endpoints
- docs/NIP-XX-CASHU-ACCESS-TOKENS.md: Full specification

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 11:30:11 +02:00
parent 2eb523c161
commit ea4a54c5e7
18 changed files with 4129 additions and 0 deletions

View File

@@ -143,6 +143,14 @@ type C struct {
BunkerEnabled bool `env:"ORLY_BUNKER_ENABLED" default:"false" usage:"enable NIP-46 bunker signing service (requires WireGuard)"`
BunkerPort int `env:"ORLY_BUNKER_PORT" default:"3335" usage:"internal port for bunker WebSocket (only accessible via WireGuard)"`
// Cashu access token configuration (NIP-XX)
CashuEnabled bool `env:"ORLY_CASHU_ENABLED" default:"false" usage:"enable Cashu blind signature tokens for access control"`
CashuTokenTTL string `env:"ORLY_CASHU_TOKEN_TTL" default:"168h" usage:"token validity duration (default: 1 week)"`
CashuKeysetTTL string `env:"ORLY_CASHU_KEYSET_TTL" default:"168h" usage:"keyset active signing period (default: 1 week)"`
CashuVerifyTTL string `env:"ORLY_CASHU_VERIFY_TTL" default:"504h" usage:"keyset verification period (default: 3 weeks)"`
CashuScopes string `env:"ORLY_CASHU_SCOPES" default:"relay,nip46" usage:"comma-separated list of allowed token scopes"`
CashuReauthorize bool `env:"ORLY_CASHU_REAUTHORIZE" default:"true" usage:"re-check ACL on each token verification for stateless revocation"`
// Cluster replication configuration
ClusterPropagatePrivilegedEvents bool `env:"ORLY_CLUSTER_PROPAGATE_PRIVILEGED_EVENTS" default:"true" usage:"propagate privileged events (DMs, gift wraps, etc.) to relay peers for replication"`
@@ -523,3 +531,54 @@ func (cfg *C) GetWireGuardConfigValues() (
cfg.BunkerEnabled,
cfg.BunkerPort
}
// GetCashuConfigValues returns the Cashu access token configuration values.
// This avoids circular imports with pkg/cashu while allowing main.go to construct
// the Cashu issuer/verifier configuration.
func (cfg *C) GetCashuConfigValues() (
enabled bool,
tokenTTL time.Duration,
keysetTTL time.Duration,
verifyTTL time.Duration,
scopes []string,
reauthorize bool,
) {
// Parse token TTL
tokenTTL = 168 * time.Hour // Default: 1 week
if cfg.CashuTokenTTL != "" {
if d, err := time.ParseDuration(cfg.CashuTokenTTL); err == nil {
tokenTTL = d
}
}
// Parse keyset TTL
keysetTTL = 168 * time.Hour // Default: 1 week
if cfg.CashuKeysetTTL != "" {
if d, err := time.ParseDuration(cfg.CashuKeysetTTL); err == nil {
keysetTTL = d
}
}
// Parse verify TTL
verifyTTL = 504 * time.Hour // Default: 3 weeks
if cfg.CashuVerifyTTL != "" {
if d, err := time.ParseDuration(cfg.CashuVerifyTTL); err == nil {
verifyTTL = d
}
}
// Parse scopes
if cfg.CashuScopes != "" {
scopes = strings.Split(cfg.CashuScopes, ",")
for i := range scopes {
scopes[i] = strings.TrimSpace(scopes[i])
}
}
return cfg.CashuEnabled,
tokenTTL,
keysetTTL,
verifyTTL,
scopes,
cfg.CashuReauthorize
}