perf(mobile): reuse the derived pubkey when signing auth events (#8460)

## Summary

Each relay `/query` signs a NIP-98 event, and each relay media image GET
signs a Blossom auth event. Both run on the UI isolate. `nostr`'s
`Event.from` derives the pubkey from the secret key on every call, which
is about a third of the cost of a signature.

Both call sites now pass the pubkey that `myPubkeyProvider` already
caches for the active key. The provider is derived from the same
`relayConfigProvider` state as the key, so the two always match.

This is the quick win. Moving signing to native secp256k1 or a worker
isolate would remove the rest of the cost, and that would be a separate
change.

## Before and after

AOT-compiled benchmark on Apple silicon, `nostr` 2.0.0 `Event.from`,
three runs of 50 signatures each:

| | ms per signature |
|---|---|
| `main` (pubkey derived) | 8.8 to 9.6 |
| this PR (pubkey passed) | 5.9 to 6.4 |

A scroll that shows 20 uncached relay-media images now spends about 60
ms less on the UI thread.

## Test

There are no new tests. The existing `queryRelay` test runs through the
real `relayConfigProvider` and `myPubkeyProvider`, and asserts the
signed event's pubkey for one identity. It does not verify the signature
or switch identities, and it would still pass with this optimization
removed. The media tests build the service directly, so they only cover
the fallback where the pubkey is derived.

Review traced the pairing instead. Both the key and the pubkey come from
`relayConfigProvider`. `_postForEvents` reads both and signs with no
await in between, and the media provider watches both. So the key and
pubkey change together on community switch and sign-out.

## Validation

- `flutter analyze`
- Full mobile suite: 3,534 passed.

**Human test:** Wes ran #8456, #8460 and #8462 together on the simulator
with his real account and confirmed they work.

buzz-review-completed

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

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Wes
2026-10-19 15:10:14 -07:00
committed by GitHub
parent 52aa37a929
commit 91b1cc4761
3 changed files with 15 additions and 1 deletions

View File

@@ -31,6 +31,7 @@ const _mediaGetAuthRefreshMarginSeconds = 60;
class MediaGetAuthService {
final String _baseUrl;
final String? _nsec;
final String? _pubkey;
final DateTime Function() _now;
Map<String, String>? _cachedHeaders;
@@ -39,9 +40,11 @@ class MediaGetAuthService {
MediaGetAuthService({
required String baseUrl,
required String? nsec,
String? pubkey,
DateTime Function()? now,
}) : _baseUrl = baseUrl,
_nsec = nsec,
_pubkey = pubkey,
_now = now ?? DateTime.now;
bool isRelayMediaUrl(String url) {
@@ -122,6 +125,8 @@ class MediaGetAuthService {
content: 'Get buzz-media',
tags: tags,
secretKey: privkeyHex,
// Deriving the pubkey costs about a third of a signature, per image.
pubkey: _pubkey,
verify: false,
);
}
@@ -129,7 +134,11 @@ class MediaGetAuthService {
final mediaGetAuthServiceProvider = Provider<MediaGetAuthService>((ref) {
final config = ref.watch(relayConfigProvider);
return MediaGetAuthService(baseUrl: config.baseUrl, nsec: config.nsec);
return MediaGetAuthService(
baseUrl: config.baseUrl,
nsec: config.nsec,
pubkey: ref.watch(myPubkeyProvider),
);
});
Map<String, String> mediaGetHeadersFor(WidgetRef ref, String url) {

View File

@@ -245,6 +245,7 @@ class RelaySessionNotifier extends Notifier<SessionState> {
url: url,
bodyBytes: bodyBytes,
nsec: config.nsec,
pubkey: ref.read(myPubkeyProvider),
),
'Content-Type': 'application/json',
},

View File

@@ -1,10 +1,13 @@
part of 'relay_session.dart';
/// Pass the signer's hex [pubkey] when it is already known: deriving it from
/// the key costs about a third of a signature, on the UI isolate.
String buildNip98AuthHeader({
required String method,
required String url,
required List<int> bodyBytes,
required String? nsec,
String? pubkey,
}) {
if (nsec == null || nsec.isEmpty) {
throw Exception('Cannot query relay: no signing key available');
@@ -27,6 +30,7 @@ String buildNip98AuthHeader({
['nonce', const Uuid().v4()],
],
secretKey: privkeyHex,
pubkey: pubkey,
verify: false,
);
return 'Nostr ${base64.encode(utf8.encode(event.toJson()))}';