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]
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:
The cause is
Cargo.toml:78:That size-optimises every dependency,
curve25519-dalekincluded. It is a deliberate choice for contract size, and it applies to the shipped WASM.The trade-off, measured
Raising
opt-levelto 3 for the two dalek crates only:opt-level = 'z'opt-level = 3room_contract.wasmchat_delegate.wasmWhy 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 singlebanned_member_idscall 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_idsderivation 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]