perf(mobile): stop re-deriving the pubkey on every event signature - #6480
Draft
odedlaz wants to merge 1 commit into
Draft
perf(mobile): stop re-deriving the pubkey on every event signature#6480odedlaz wants to merge 1 commit into
odedlaz wants to merge 1 commit into
Conversation
`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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Event.fromderives 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 (
nostroverpointycastle), with no native secp256k1 binding. Measured host-AOT on an M-series machine,dart compile exe, n=300:Event.fromas the call sites used itEvent.fromwithpubkey:suppliedA 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.dartchecks 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.derivePublicKeyalready 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/awaityields 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 analyzeclean,dart formatclean, and the full mobile suite green — 1548 tests, including the pairing and relay-session suites that cover the touched call sites.