decent-newsroom / signing-bundle
Server-side Nostr signing orchestration for extension, relay AUTH, and NIP-46 bunker flows.
Package info
github.com/decent-newsroom/signing-bundle
Type:symfony-bundle
pkg:composer/decent-newsroom/signing-bundle
Requires
- php: >=8.3
- ext-ctype: *
- ext-json: *
- ext-openssl: *
- ext-redis: *
- decent-newsroom/nostr-client-bundle: @dev
- endroid/qr-code: ^6.0
- innis/nostr-core: ^0.3.17
- psr/log: ^3.0
- swentel/nostr-php: ^1.9
- symfony/config: ^7.4
- symfony/dependency-injection: ^7.4
- symfony/framework-bundle: ^7.4
- symfony/http-foundation: ^7.4
- symfony/routing: ^7.4
- symfony/uid: ^7.4
Requires (Dev)
- phpunit/phpunit: ^9.6
This package is auto-updated.
Last update: 2026-08-23 09:04:02 UTC
README
Reusable Symfony bundle for Nostr signing orchestration in Decent Newsroom.
The bundle answers one question:
Can the app obtain a valid Nostr signature for this pubkey, and by which method?
It does not authenticate accounts, persist published events, decide publication policy, or own long-lived relay gateway connections. Those remain host or neighbor-bundle responsibilities.
What It Owns
- Nostr Connect QR generation at
/nostr-connect/qr. - Post-auth remote signer session registration at
/api/nostr-connect/session. - Encrypted Redis storage for NIP-46 remote signer sessions.
- Server-side NIP-46
sign_eventrequests throughnostr-client-bundle. - Validation of signed events returned by a remote signer.
- Relay AUTH signing for kind
22242events. - Signer strategy discovery through the
signing.nostr_signer_strategytag.
What It Does Not Own
- User authentication, identity links, or Symfony user lookup.
- Browser extension signing for already signed publish requests.
- Mercure browser fallback for relay AUTH.
- Event persistence, moderation, policy checks, or relay publication.
- Relay gateway connection pooling and reconnect policy.
Those boundaries let NIP-07 extension flows and NIP-46 bunker flows coexist: extension users can keep posting already signed events, while bunker users can store a server-side remote signer session for background signing and relay AUTH.
Main Classes
| Area | Class |
|---|---|
| Bundle entry point | DecentNewsroom\SigningBundle\SigningBundle |
| Configuration | DependencyInjection\Configuration, SigningExtension |
| QR endpoint | Controller\NostrConnectController |
| Session endpoint | Controller\RemoteSignerSessionController |
| Session value object | Dto\RemoteSignerSession |
| Redis session storage | Storage\RedisRemoteSignerSessionStore |
| Compatibility session facade | Service\Nostr\Nip46SessionStore |
| NIP-46 RPC signer | Service\Nostr\Nip46EventSigner |
| NIP-46 response handler | Service\Nostr\Nip46ResponseHandler |
| Relay AUTH event builder | Service\Nostr\RelayAuthEventFactory |
| Bunker strategy | Service\Nostr\RemoteBunkerSignerStrategy |
| Strategy registry | Service\Nostr\NostrSignerStrategyRegistry |
Public Contracts
CurrentSubjectPubkeyResolverInterface- host adapter that resolves the authenticated user's hex pubkey for post-auth session storage.SignerRelayProviderInterface- host adapter that supplies signer relay URLs.RemoteSignerSessionStoreInterface- storage boundary for encrypted remote signer sessions.Nip46EventSignerInterface- low-level NIP-46sign_eventsigner.Nip46AuthEventSignerInterface- compatibility contract for signing relay AUTH events through NIP-46.NostrEventSignerInterface- generic unsigned event intent to signed event.RelayAuthSignerInterface- relay AUTH support check and kind22242signing.NostrSignerStrategyInterface- method-based signer strategy discovery.
Configuration
# config/packages/signing.yaml signing: app_name: 'Decent Newsroom' nostr_connect: requested_permissions: - 'sign_event:27235' - 'sign_event:22242' - 'get_public_key' nip46: session_ttl_seconds: 28800 request_timeout_seconds: 15 redis_prefix: 'nip46_session:' encryption_key: '%env(APP_ENCRYPTION_KEY)%'
Host Wiring
The host application wires local policy and account context into the bundle:
DecentNewsroom\SigningBundle\Contract\SignerRelayProviderInterface: alias: App\Service\Nostr\RelayRegistry DecentNewsroom\SigningBundle\Contract\CurrentSubjectPubkeyResolverInterface: alias: App\Signing\AppCurrentSubjectPubkeyResolver
Routes are imported from the package:
signing_bundle: resource: '../../packages/signing-bundle/src/Resources/config/routes.yaml'
Supported Flows
Extension Publishing
Browser JavaScript signs events with NIP-07 and posts already signed events to the backend. SigningBundle does not sign in this path. The host still verifies the event id, signature, pubkey, and account relationship before persistence or relay publishing.
Extension Relay AUTH
RelayGatewayBundle asks the host for a signed kind 22242 AUTH event. The host
tries RelayAuthSignerInterface first. If no NIP-46 session exists, the host may
fall back to Mercure and browser-side extension signing.
Bunker Publishing And Relay AUTH
After pairing, the browser posts NIP-46 session material to
/api/nostr-connect/session. The bundle stores the encrypted session under the
authenticated subject pubkey. Later, server-side publish intent or relay AUTH can
use RemoteBunkerSignerStrategy to reach the bunker directly.
The signer validates returned events before handing them back:
- returned pubkey must match the requested subject pubkey;
- signed event fields must match the unsigned intent;
- event id and signature must validate through
innis/nostr-core.
Documentation
Testing
Focused package tests can run without booting the host kernel:
vendor/bin/phpunit packages/signing-bundle/tests/Service/Nostr
Project-level validation should still run inside Docker when the app container is available:
docker compose exec php bin/console lint:container docker compose exec php bin/phpunit