Skip to content

ed25519 verify costs 2373 us because opt-level='z' size-optimises curve25519-dalek — 61x available for +2% WASM #681

Description

@sanity

A bare ed25519 verify in River's release build takes 2373 µs

That is roughly 50× what it should be, and it is not River's code. The whole path is one signature verification plus about half a microsecond of serialization:

bare ed25519 verify (this build):              2373 us
ciborium serialize a UserBan:                     0.6 us
ban_signature_matches_current_key:             2430 us

The cause is Cargo.toml:78:

[profile.release.package."*"]
opt-level = 'z'     # Optimize all dependencies for size as well

That size-optimises every dependency, curve25519-dalek included. It is a deliberate choice for contract size, and it applies to the shipped WASM.

The trade-off, measured

Raising opt-level to 3 for the two dalek crates only:

opt-level = 'z' dalek at opt-level = 3
ed25519 verify 2373 µs 39 µs 61× faster
room_contract.wasm 794,190 B 809,307 B +15 KB (+1.90%)
chat_delegate.wasm 736,954 B 755,685 B +19 KB (+2.54%)

Why it is worth raising as its own issue

Every signature verification in the contract pays that 61×, not just one path — verify() walks every member, every message, every ban and every DM. On Official-room-sized state (93 members, 200 bans at a cap of 200, 300 DMs) a single banned_member_ids call is 471.8 ms, essentially all of it 200 sequential verifies, and a whole-state merge takes ~4.25 s. That is very likely the dominant term in merge cost network-wide.

Concretely: the #675 remedy in #673 adds a second banned_member_ids derivation per apply, measured at +12.3% of merge time. With the profile fixed the same change costs about 0.2%. An optimisation was designed and rejected during that review specifically because it was optimising around this artifact rather than around anything real.

Two caveats, neither of which I can close

These are native measurements, and the contract runs under wasmtime. The ratio very likely carries — the mechanism is the same size-optimised curve arithmetic — but it has not been measured in the runtime that matters. Anyone acting on this should measure there first; the native number is the reason to look, not the number to quote.

The size increase is a real cost against a deliberate policy. +1.9% and +2.5% are not free for a contract whose bytes are its identity and whose size the project has chosen to optimise. This is a trade-off, not an obvious win, and it belongs to whoever owns that policy rather than to a reviewer who happened to trip over it.

Suggested next step

Measure a verify under wasmtime at both profile settings before changing anything. If the ratio holds, the question is whether ~34 KB across both artifacts is worth a 61× reduction in the dominant cost of every contract operation — and that is a judgement call, not a bug fix.

Note also that changing the profile re-keys both artifacts, so it needs the usual migration path (legacy_room_contracts.toml, legacy_delegates.toml, pointer re-sign) and should ride a release that is re-keying anyway rather than forcing its own.

Found while reviewing #673. Related: #675, #678.

[AI-assisted - Claude]

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions