code wiki / (root) / x25519.nx

x25519.nx

buildroot/runtime/x25519.nx

19358 B485 linesdepth 3pulls 3 transitivereach 1 importersview sourcekind tooltopic x25519
docsdependenciesstructsconstsfunctions

about

x25519.nx -- Bernstein's Curve25519 X25519 (RFC 7748). Elliptic-curve Diffie-Hellman on Curve25519 in Montgomery form. Primary classical KEX for TLS 1.3, SSH, Signal, WireGuard, and every modern protocol that cares about correctness. Hybrid- deploys alongside ML-KEM-768 for post-quantum TLS 1.3 (RFC 9578 X25519Kyber768Draft00). Field: GF(p) with p = 2^255 - 19. Curve equation: y^2 = x^3 + 486662*x^2 + x (Montgomery form). Scalar input: 32 bytes, clamped per RFC 7748 §5. u-coordinate input: 32 bytes, high bit cleared per §5. Output: 32-byte shared secret. Representation: 10 x 25.5-bit limbs (Bernstein ref10 style). Offsets: 0, 26, 51, 77, 102, 128, 153, 179, 204, 230 (bits). Limb widths alternate 26 / 25 bits so 10 * 25.5 = 255 bits. Each limb fits in i64 comfortably (26 bits plus headroom). Multiplication of two limbs: 26 + 26 = 52 bits -- well within i64 range. Accumulating up to 10 partial products per output digit: ~56 bits peak -- still within i64. Why 10x25.5 and not 5x51: NishiLang has no u128 / 128-bit multiply. 5x51-bit limbs give 102-bit products that don't fit in i64. The 10x25.5 layout avoids this entirely; every intermediate fits in a plain i64. Constant-time discipline: X25519 MUST run in time independent of scalar/u bits (leaking would recover the private key). The Montgomery ladder + cswap achieves this naturally: same instruction sequence regardless of bit values; conditional swap via bitmask select rather than branch. No table lookups on secret bits. No divisions (used only in fe_invert, which runs over a fixed chain of squarings and multiplications regardless of input). Invariants (enforced, not hoped): XC1 Scalar clamping applied once at entry per RFC 7748 §5: scalar[0] &= 0xf8; scalar[31] &= 0x7f; scalar[31] |= 0x40. XC2 u-coordinate high bit cleared at entry (RFC 7748 §5).

dependencies 1 imports · 1 importers

syscalls.nx x25519.nx x25519_ephemeral.nx

imports: syscalls.nx

imported by: x25519_ephemeral.nx

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main x25519 fe_alloc fe_from_bytes fe_one fe_zero fe_zero ↻ fe_copy x25519_ladder_step fe_cswap fe_alloc ↻ fe_add fe_sq fe_mul fe_sub fe_mul ↻ fe_mul_a24 fe_alloc ↻ fe_mul ↻ fe_cswap ↻ fe_invert fe_alloc ↻ fe_sq ↻ fe_mul ↻ fe_mul ↻ fe_to_bytes fe_alloc ↻ fe_copy ↻

structs

none

consts

61const FE_LIMBS: i64 = 10

functions

63func fe_alloc() -> *i64 {
68func fe_copy(dst: *i64, src: *i64) -> i64 {
called by 2: fe_to_bytesx25519
74func fe_zero(h: *i64) -> i64 {
called by 2: fe_onex25519
80func fe_one(h: *i64) -> i64 {
called by 1: x25519 calls 1: fe_zero
92func fe_add(h: *i64, f: *i64, g: *i64) -> i64 {
called by 1: x25519_ladder_step
98func fe_sub(h: *i64, f: *i64, g: *i64) -> i64 {
called by 1: x25519_ladder_step
117func fe_mul(h: *i64, f: *i64, g: *i64) -> i64 {
200func fe_sq(h: *i64, f: *i64) -> i64 {
called by 2: fe_invertx25519_ladder_step calls 1: fe_mul
206func fe_mul_a24(h: *i64, f: *i64) -> i64 {
called by 1: x25519_ladder_step calls 2: fe_allocfe_mul
220func fe_cswap(f: *i64, g: *i64, swap: i64) -> i64 {
238func fe_invert(out: *i64, z: *i64) -> i64 {
called by 1: x25519 calls 3: fe_allocfe_sqfe_mul
279func fe_from_bytes(h: *i64, s: *u8) -> i64 {
called by 1: x25519
298func fe_to_bytes(s: *u8, h_in: *i64) -> i64 {
called by 1: x25519 calls 2: fe_allocfe_copy
369func x25519_ladder_step(x1: *i64,
421func x25519(scalar: *u8, u: *u8, out: *u8) -> i64 {
477func main() -> i64 {
calls 1: x25519