4.0 KiB
primitives/ristretto255
(no submodules)
Purpose
The ristretto255 namespace exposes libsodium's Ristretto255 group operations — a prime-order group construction over Curve25519 that eliminates the cofactor pitfalls present in raw Edwards25519 arithmetic. Ristretto255 is used in zero-knowledge proofs, Oblivious Pseudorandom Functions (OPRFs), Password-Authenticated Key Exchanges (PAKEs), and Verifiable Random Functions (VRFs).
By wrapping libsodium's crypto_core_ristretto255_* and crypto_scalarmult_ristretto255_* APIs, this module provides the building blocks for advanced cryptographic protocols without requiring callers to reason about low-level curve arithmetic or cofactor clearing.
Features
- Group element addition (
add) and subtraction (sub) viacrypto_core_ristretto255_add/sub. - Scalar multiplication (
mul) viacrypto_scalarmult_ristretto255. - Hash-to-curve via
fromHashusingcrypto_core_ristretto255_from_hash(64-byte input). - Point serialisation (
toBytes) and deserialisation with validation (fromBytes). - Random group element generation (
random) viacrypto_core_ristretto255_random. - All operations are async and call
sodiumReady()internally. fromBytesthrowsRangeErrorfor invalid or identity points.
Usage: Basic Example
import { ristretto255 } from "@suaveplan/crypto-sodium/primitives/ristretto255";
// Generate two random group elements
const p = await ristretto255.random();
const q = await ristretto255.random();
// Add them
const sum = await ristretto255.add(p, q);
// Verify commutativity
const sumQP = await ristretto255.add(q, p);
// sum deepEquals sumQP
Usage: Advanced Example
Hash-to-curve for OPRF input blinding:
import { ristretto255 } from "@suaveplan/crypto-sodium/primitives/ristretto255";
// Hash the password to a group element (hash-to-curve)
// In a real OPRF you'd use a proper H2C function; fromHash works for demos
async function hashToPoint(input: Uint8Array): Promise<Uint8Array> {
// Pad/hash input to 64 bytes with SHA-512 first in production:
const padded = new Uint8Array(64);
padded.set(input.subarray(0, Math.min(input.length, 64)));
return ristretto255.fromHash(padded);
}
// Scalar multiplication: client blinds the point with a random scalar
const blindScalar = new Uint8Array(32);
crypto.getRandomValues(blindScalar);
const point = await hashToPoint(new TextEncoder().encode("password"));
const blinded = await ristretto255.mul(blindScalar, point);
API Reference
ristretto255.add(p, q): Promise<Uint8Array>
Adds two group elements. Commutative: add(p, q) === add(q, p).
ristretto255.sub(p, q): Promise<Uint8Array>
Subtracts q from p. Inverse of add.
ristretto255.mul(scalar, point): Promise<Uint8Array>
Scalar multiplication: computes scalar * point. Scalar is a 32-byte little-endian integer.
ristretto255.fromHash(hash): Promise<Uint8Array>
Maps a 64-byte hash uniformly to a group element via crypto_core_ristretto255_from_hash.
ristretto255.toBytes(point): Promise<Uint8Array>
Returns a copy of the 32-byte canonical encoding of point.
ristretto255.fromBytes(bytes): Promise<Uint8Array>
Validates and returns a group element. Throws RangeError if bytes is not a valid, non-identity Ristretto255 point.
ristretto255.random(): Promise<Uint8Array>
Generates a uniformly random 32-byte group element.
Implementation Notes
Ristretto255 represents each group element as a 32-byte canonical encoding. Unlike raw Edwards25519, every 32-byte encoding maps to at most one group element — there are no encoding ambiguities or cofactor issues.
fromBytes calls crypto_core_ristretto255_is_valid_point before returning, which rejects the identity element (all-zeros) and any bytes that do not correspond to a valid Ristretto255 point. This prevents callers from accidentally working with invalid inputs in downstream protocols.
All operations return new Uint8Array allocations; the input arrays are not mutated.