# 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.

| Operation | Bytes | Old median ns | Current median ns | Delta | Current p95 ns | p95 delta |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Import identity | 32 | 14,736 | 14,717 | -0.13% | 14,857 | +0.53% |
| Read public key | 0 | 123 | 127 | +3.25% | 129 | +0.78% |
| NIP-44 encrypt | 32 | 34,377 | 34,496 | +0.35% | 34,620 | -0.24% |
| NIP-44 decrypt | 32 | 33,821 | 33,874 | +0.16% | 33,925 | -3.16% |
| NIP-44 encrypt | 1,024 | 41,524 | 35,813 | -13.75% | 35,960 | -17.10% |
| NIP-44 decrypt | 1,024 | 35,191 | 35,363 | +0.49% | 35,447 | 0.00% |
| NIP-44 encrypt | 64,000 | 142,284 | 143,384 | +0.77% | 145,072 | +1.48% |
| NIP-44 decrypt | 64,000 | 145,275 | 145,413 | +0.09% | 145,858 | -0.03% |
| Sign NIP-01 event | 32 | 48,236 | 48,107 | -0.27% | 49,367 | -3.15% |
| Parse and verify event | 374 | 33,123 | 33,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:

| Operation | Bytes | Median ns | p95 ns |
| --- | ---: | ---: | ---: |
| Create NIP-59 rumor | 15 | 1,066 | 1,074 |
| Create NIP-17 text message | 15 | 6,309 | 6,322 |
| Create durable gift wrap | 15 | 237,227 | 237,817 |
| Authenticate and unwrap | 15 | 112,394 | 112,739 |
| Stream-encrypt attachment | 64,000 | 109,333 | 109,484 |

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

## Footprint

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

| Artifact | Old bytes | Current bytes | Delta |
| --- | ---: | ---: | ---: |
| `libsoftchat.so` | 2,278,048 | 3,956,496 | +73.68% |
| `libsoftchat.so`, `gzip -n` | 1,559,813 | 2,196,812 | +40.84% |
| `libsoftchat.a` | 30,290,492 | 37,198,590 | +22.81% |
| `libsoftchat.a`, `gzip -n` | 8,764,215 | 11,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 [Performance Benchmarks](../../benchmarks/README.md). 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.
