@umbra-privacy/rn-quick-x25519
v0.0.2
Published
X25519 scalar multiplication for React Native
Downloads
187
Readme
@umbra-privacy/rn-quick-x25519
X25519 scalar multiplication (RFC 7748) for React Native, implemented in C++ via Nitro Modules. The curve arithmetic is a public-domain port of TweetNaCl's crypto_scalarmult_curve25519.
This module is intentionally minimal: it exposes only the raw scalar-mult primitive. Higher-level constructions (X3DH, key derivation, AEAD) are out of scope.
Install
npm install @umbra-privacy/rn-quick-x25519 react-native-nitro-modules
# or
pnpm add @umbra-privacy/rn-quick-x25519 react-native-nitro-modulesreact-native-nitro-modules is a peer dependency. iOS requires pod install. Android picks the module up via autolinking.
Usage
import { x25519, X25519_BYTES } from '@umbra-privacy/rn-quick-x25519'
// Synchronous (number[] in/out)
const shared = x25519.scalarMult(privateKey, peerPublicKey)
// shared.length === 32
// Asynchronous (zero-copy ArrayBuffer in/out, runs off the JS thread)
const sharedBuf = await x25519.scalarMultAsync(privateKeyBuf, peerPublicKeyBuf)
// sharedBuf.byteLength === 32All inputs and outputs are exactly 32 bytes, little-endian. The scalar is clamped per RFC 7748 inside the native code, so callers may pass any 32 random bytes as a private key.
API
x25519.scalarMult(scalar: number[], point: number[]): number[]
Synchronous scalar multiplication. Both arguments must be 32-element arrays of byte values (0–255); throws Error otherwise. Runs on the calling JS thread.
x25519.scalarMultAsync(scalar: ArrayBuffer, point: ArrayBuffer): Promise<ArrayBuffer>
Same operation, but:
- inputs/outputs are
ArrayBuffer(no per-byte marshaling), - the curve operation runs on a background thread, so multiple calls awaited via
Promise.allparallelize across cores.
Both arguments must have byteLength === 32; the promise rejects otherwise.
X25519_BYTES
Constant 32 — the size of every scalar, point, and shared secret.
Choosing between the two
| | scalarMult | scalarMultAsync |
|---|---|---|
| Input type | number[] | ArrayBuffer |
| Threading | JS thread | worker thread |
| Best for | one-off calls, small surfaces | batches, latency-sensitive UI, concurrent operations |
Prefer the async + ArrayBuffer path when doing more than a handful of operations or when the caller already has bytes in a buffer (e.g. from expo-crypto, react-native-quick-crypto, or a fetch response).
Security notes
- The implementation uses TweetNaCl's constant-time field arithmetic. No data-dependent branches or memory accesses on secret values.
- The output of
scalarMultis not checked against the all-zero shared secret (RFC 7748 §6.1). If your protocol can receive untrusted public keys and is not naturally contributory, perform that check at the protocol layer. - Key material in scratch buffers is not zeroized after use. If that matters in your threat model, file an issue — it's a small change to apply across both code paths.
Project layout
src/specs/X25519.nitro.ts— Nitro spec (TypeScript).cpp/HybridX25519.{hpp,cpp}— C++ binding glue.cpp/curve25519.{hpp,cpp}— TweetNaCl-derived field/curve implementation.nitrogen/— generated Nitro bindings; checked in.
License
MIT. The bundled curve25519 code is public domain (TweetNaCl, Bernstein et al.).
