Skip to main content

Rust Performance

Finding

The final Rust API is performance-neutral or faster than the frozen SDK baseline. Excluding a 123-ns getter, the largest slower median is 1.35%; 1-KiB encryption is 13.75% faster. No shared Rust workload identifies a release regression.

The audit used three paired optimized processes on a GitHub-hosted Ubuntu 24.04 x86_64 runner with Rust 1.97.0. Values are nanoseconds per public API call. Negative deltas mean the current SDK is faster.

OperationBytesOld median nsCurrent median nsDeltaCurrent p95 nsp95 delta
Import identity3214,73614,717-0.13%14,857+0.53%
Read public key0123127+3.25%129+0.78%
NIP-44 encrypt3234,37734,496+0.35%34,620-0.24%
NIP-44 decrypt3233,82133,874+0.16%33,925-3.16%
NIP-44 encrypt1,02441,52435,813-13.75%35,960-17.10%
NIP-44 decrypt1,02435,19135,363+0.49%35,4470.00%
NIP-44 encrypt64,000142,284143,384+0.77%145,072+1.48%
NIP-44 decrypt64,000145,275145,413+0.09%145,858-0.03%
Sign NIP-01 event3248,23648,107-0.27%49,367-3.15%
Parse and verify event37433,12333,570+1.35%33,729-1.60%

The public-key measurement is below a useful timing floor and is retained only as an API-shape check.

Current-only baseline

The old SDK has no equivalent for these complete workflows:

OperationBytesMedian nsp95 ns
Create NIP-59 rumor151,0661,074
Create NIP-17 text message156,3096,322
Create durable gift wrap15237,227237,817
Authenticate and unwrap15112,394112,739
Stream-encrypt attachment64,000109,333109,484

These values establish descriptive capability baselines; they are not old-versus-new speedup claims.

Footprint

The complete API necessarily links substantially more protocol code than the old initial surface. Deterministic local Linux builds measured:

ArtifactOld bytesCurrent bytesDelta
libsoftchat.so2,278,0483,956,496+73.68%
libsoftchat.so, gzip -n1,559,8132,196,812+40.84%
libsoftchat.a30,290,49237,198,590+22.81%
libsoftchat.a, gzip -n8,764,21511,190,523+27.68%

The dynamic-library number includes symbols. Shipping Android libraries are stripped while unstripped copies are retained separately for crash symbolication. Do not compare these host files with installed app size.

Optimizations retained

  • Native Rust callers use LocalIdentity directly; foreign-language ownership stays in LocalIdentityHandle.
  • The Android JNI weak-handle registry is compiled only for Android JNI builds. Rust, Swift, JVM, and Wasm do not pay its lock, map, or shared-state cost.
  • Imported foreign identities return immutable public metadata once rather than re-deriving or repeatedly crossing FFI.
  • NIP-44 keeps the vectored legacy-prefix implementation and bounded extended-prefix fallback.
  • Attachment encryption and decryption process bounded chunks rather than collecting a complete file in memory.
  • Relay, Noise, Negentropy, and envelope APIs cross the boundary as complete operations rather than callback-heavy primitives.

Developer guidance

  • Construct LocalIdentity once per authenticated session. Clear the caller's secret buffer after import and do not parse the same secret per message.
  • Parse remote keys, event IDs, filters, and metadata at trust boundaries and retain validated typed values.
  • Keep event, envelope, relay-session, and reconciliation operations coarse. Do not split them into per-tag or per-field calls for a foreign consumer.
  • Move large batches off latency-sensitive executor threads. Public protocol operations are synchronous CPU work.
  • Stream attachments and stage decrypted output until final authentication; do not buffer an unbounded file.
  • Never cache plaintext, private keys, derived conversation keys, or unauthenticated envelope layers for a benchmark. A future derived-key cache requires a separate lifetime, concurrency, and zeroization design.

Run shared and current-only measurements through the immutable workflow or the commands in the Performance Benchmarks source guide. Any crypto, serialization, payload-limit, FFI-shape, dependency, or release-profile change reruns every language, correctness vector, and artifact-size audit.

Open work

Rust latency is not an active blocker. The remaining native concern is footprint: evaluate dependency convergence and release-link settings only with all vectors, foreign-language audits, symbols, and package consumers rerun.