// X25519 (RFC 7748) — the Curve25519 Diffie-Hellman that produces the ECDHE shared
// secret feeding the TLS 1.3 / QUIC handshake key schedule. The Montgomery ladder runs
// over the field mod 2^255-19 on core BigInt; the scalar drives the ladder from its
// clamped bytes, so no BigInt bit operations are needed. Subtractions add the prime
// first to stay non-negative rather than assume a Euclidean remainder.
///|
/// The Curve25519 field prime, 2^255 - 19 (RFC 7748 §4.1).
let x25519_p : BigInt = (1N << 255) - 19N
///|
/// The ladder constant (A-2)/4 = 121665 for Curve25519.
let x25519_a24 : BigInt = 121665N
///|
/// Little-endian bytes to a field integer.
fn x25519_le_to_int(b : Array[Int]) -> BigInt {
let mut acc = 0N
for i = b.length() - 1; i >= 0; i = i - 1 {
acc = acc * 256N + BigInt::from_int(b[i])
}
acc
}
///|
/// A field integer to 32 little-endian bytes.
fn x25519_int_to_le32(v : BigInt) -> Bytes {
let out = Buffer()
let mut acc = v % x25519_p
for _i = 0; _i < 32; _i = _i + 1 {
out.write_byte((acc % 256N).to_int().to_byte())
acc = acc / 256N
}
out.to_bytes()
}
///|
/// X25519 (RFC 7748 §5): the scalar `k` applied to u-coordinate `u`, both 32-byte
/// little-endian, returning the 32-byte little-endian result u-coordinate. The scalar
/// is clamped and the u-coordinate's high bit masked per §5.
pub fn x25519(k : Bytes, u : Bytes) -> Bytes {
let scalar = Array::make(32, 0)
for i = 0; i < 32; i = i + 1 {
scalar[i] = k[i].to_int()
}
scalar[0] = scalar[0] & 248
scalar[31] = scalar[31] & 127
scalar[31] = scalar[31] | 64
let ubytes = Array::make(32, 0)
for i = 0; i < 32; i = i + 1 {
ubytes[i] = u[i].to_int()
}
ubytes[31] = ubytes[31] & 127
let p = x25519_p
let x1 = x25519_le_to_int(ubytes) % p
let mut x2 = 1N
let mut z2 = 0N
let mut x3 = x1
let mut z3 = 1N
let mut swap = 0
for t = 254; t >= 0; t = t - 1 {
let kt = (scalar[t / 8] >> (t % 8)) & 1
swap = swap ^ kt
if swap == 1 {
let tx = x2
x2 = x3
x3 = tx
let tz = z2
z2 = z3
z3 = tz
}
swap = kt
let a = (x2 + z2) % p
let aa = a * a % p
let b = (x2 + p - z2) % p
let bb = b * b % p
let e = (aa + p - bb) % p
let c = (x3 + z3) % p
let d = (x3 + p - z3) % p
let da = d * a % p
let cb = c * b % p
let sum = (da + cb) % p
x3 = sum * sum % p
let diff = (da + p - cb) % p
z3 = x1 * (diff * diff % p) % p
x2 = aa * bb % p
z2 = e * ((aa + x25519_a24 * e) % p) % p
}
if swap == 1 {
let tx = x2
x2 = x3
x3 = tx
let tz = z2
z2 = z3
z3 = tz
}
// x2 / z2 = x2 * z2^(p-2) mod p (Fermat inverse).
let inv = BigInt::pow(z2, p - 2N, modulus=p)
x25519_int_to_le32(x2 * inv % p)
}
///|
/// The Curve25519 base point u=9 as a 32-byte little-endian scalar input, for turning
/// a private key into a public key: `x25519(private, x25519_base())`.
pub fn x25519_base() -> Bytes {
let b = Buffer()
b.write_byte(b'\x09')
for _i = 0; _i < 31; _i = _i + 1 {
b.write_byte(b'\x00')
}
b.to_bytes()
}