Skip to content

perf(mobile): stop re-deriving the pubkey on every event signature - #6480

Draft
odedlaz wants to merge 1 commit into
block:mainfrom
odedlaz:olazar/mobile-signing-pubkey
Draft

perf(mobile): stop re-deriving the pubkey on every event signature#6480
odedlaz wants to merge 1 commit into
block:mainfrom
odedlaz:olazar/mobile-signing-pubkey

Conversation

@odedlaz

@odedlaz odedlaz commented Aug 21, 2026

Copy link
Copy Markdown

Event.from derives the signer's pubkey whenever the caller omits it — a second elliptic-curve multiply on top of the signature, for a value that is constant per identity. None of the nine call sites supplied it.

That is not free here: the crypto is pure-Dart bigint math (nostr over pointycastle), with no native secp256k1 binding. Measured host-AOT on an M-series machine, dart compile exe, n=300:

variant p50 p95
Event.from as the call sites used it 8.13ms 8.93ms
Event.from with pubkey: supplied 5.41ms 5.50ms

A 120Hz frame is 8.333ms, and none of this work runs on another isolate — the app contains exactly one compute() and it parses emoji data. The sites that matter most are the NIP-98 header built for every authenticated HTTP request (relay_session.dart, reached from channel pagination, thread open, and mention autocomplete while typing), NIP-42 AUTH on every reconnect (relay_socket.dart), and message send (signed_event_relay.dart).

The change adds a memoized derive — one multiply per identity instead of one per event — and supplies the result at all nine sites.

Why this is behaviour-identical

Asserted rather than argued, since it is crypto-adjacent. test/shared/relay/event_signing_test.dart checks that the same secret key and timestamp produce the same event id, the same pubkey, and a valid signature whether the pubkey is supplied or derived. Event's id hash lower-cases the pubkey before hashing, so a supplied key cannot change the id or the signature; Schnorr.derivePublicKey already returns lower-case hex, so the serialized field is unchanged too.

The cache holds one key. The app signs with one identity at a time and switches on community change, so a single slot hits on every call in a run of signatures without retaining keys it no longer needs.

Scope

This reduces the per-signature cost. It does not move signing off the UI isolate — async/await yields within the same isolate, so offloading has to be explicit, and at 5.41ms the remaining cost is still about 65% of a 120Hz frame. That is a separate change.

The host-AOT figures bound the order of magnitude; they are not device numbers, and an on-device measurement can only be worse. iOS profiling is blocked on tooling in my environment, so the frame-level confirmation is still outstanding.

Verification

flutter analyze clean, dart format clean, and the full mobile suite green — 1548 tests, including the pairing and relay-session suites that cover the touched call sites.

`Event.from` derives the signer pubkey when the caller omits it, which is a
second elliptic-curve multiply on top of the signature — for a value that is
constant per identity. None of the nine call sites supplied it.

That is not free on this stack: the crypto is pure-Dart bigint math with no
native secp256k1 binding. Measured host-AOT on an M-series machine, one
`Event.from` is 8.13ms p50, against 5.41ms with the pubkey supplied. A 120Hz
frame is 8.333ms, and none of this work is on another isolate — the app has one
`compute()` and it parses emoji. The hot sites are the NIP-98 header built for
every authenticated HTTP request, NIP-42 AUTH on every reconnect, and message
send.

Adds a memoized derive so it costs one multiply per identity rather than one
per event, and supplies it at all nine sites. Behaviour-identical, and the test
asserts that rather than arguing it: same secret key and timestamp, supplied
versus derived, yields the same event id, the same pubkey, and both verify.
The id hash lower-cases the pubkey before hashing, so casing cannot drift.

This reduces the per-signature cost; it does not move signing off the UI
isolate, which is a separate change.

Signed-off-by: Oded Lazar <olazar@neo.ai>
Co-authored-by: Oded Lazar <olazar@neo.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant