Files
skills/docs/security-stack/crypto/crypto-sodium/src/primitives/ristretto255/ristretto255.md
2026-05-29 08:20:56 +01:00

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) via crypto_core_ristretto255_add/sub.
  • Scalar multiplication (mul) via crypto_scalarmult_ristretto255.
  • Hash-to-curve via fromHash using crypto_core_ristretto255_from_hash (64-byte input).
  • Point serialisation (toBytes) and deserialisation with validation (fromBytes).
  • Random group element generation (random) via crypto_core_ristretto255_random.
  • All operations are async and call sodiumReady() internally.
  • fromBytes throws RangeError for 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.