ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
curve25519.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file curve25519.h
6 * @brief Curve25519 field arithmetic + X25519 (RFC 7748) for the curve25519-sha256 KEX.
7 *
8 * Field elements are GF(2^255 - 19) in a portable radix-2^16 representation (sixteen
9 * int64 limbs), so no 128-bit integer type is needed - important because 32-bit xtensa
10 * (ESP32) gcc has no __int128. The same field arithmetic backs Ed25519 (protocore_ed25519),
11 * so the field ops are exported here. Correctness is pinned to the RFC 7748 §5.2 test
12 * vectors (test_ed25519).
13 *
14 * Two surfaces, and they are not the same kind of thing: the field ops below are shared internals that
15 * Ed25519 links against directly, and X25519 is the namespace this module exports.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef PROTOCORE_CURVE25519_H
22#define PROTOCORE_CURVE25519_H
23
24#include "protocore_config.h" // the entry point: protocore_types.h for the widths and PROTOCORE_BEGIN_DECLS
25
26#if PROTOCORE_ENABLE_CURVE25519
27
29
30// --- Field arithmetic (shared with protocore_ed25519) ----------------------------
31//
32// Not the namespace: these are the field layer X25519 below and Ed25519 are both written in. Each one
33// carries nothing across a call and takes its operands directly, so none of them needs a borrow.
34
35/** @brief A field element of GF(2^255 - 19): 16 limbs, radix 2^16 (limb i weighs 2^(16i)). */
36typedef int64_t protocore_gf[16];
37
38void protocore_gf_copy(protocore_gf out, const protocore_gf in); ///< out = in
39void protocore_gf_add(protocore_gf out, const protocore_gf a, const protocore_gf b); ///< out = a + b (unreduced)
40void protocore_gf_sub(protocore_gf out, const protocore_gf a, const protocore_gf b); ///< out = a - b (unreduced)
41void protocore_gf_mul(protocore_gf out, const protocore_gf a, const protocore_gf b); ///< out = a * b mod p
42void protocore_gf_sq(protocore_gf out, const protocore_gf a); ///< out = a^2 mod p
43void protocore_gf_inv(protocore_gf out, const protocore_gf a); ///< out = a^-1 mod p (= a^(p-2))
44void protocore_gf_pack(uint8_t out[32], const protocore_gf a); ///< canonical little-endian 32-byte encoding
45void protocore_gf_unpack(protocore_gf out, const uint8_t in[32]); ///< decode 32 bytes (high bit ignored)
46void protocore_gf_cswap(protocore_gf p, protocore_gf q, int b); ///< constant-time conditional swap of p,q when b==1
47
48// --- X25519 (RFC 7748) -----------------------------------------------------
49
50// PROTOCORE_CURVE25519_BORROW - the bytes a scalar multiplication runs out of - is stated in
51// protocore_config.h, which sums it into the secure arena. A caller takes them once and passes the
52// pointer to every call.
53
54/** @brief The scalar and the point one X25519 runs over. */
55typedef struct
56{
57 const uint8_t *scalar; ///< 32 bytes little-endian, clamped internally
58 const uint8_t *point; ///< 32 bytes little-endian u coordinate
59 uint8_t *out; ///< 32 bytes; aliases neither input
60} Curve25519X25519Args;
61
62/** @brief The scalar one X25519 against the standard base point u=9 runs over. */
63typedef struct
64{
65 const uint8_t *scalar; ///< 32 bytes little-endian, clamped internally
66 uint8_t *out; ///< 32 bytes
67} Curve25519X25519BaseArgs;
68
69/**
70 * @brief X25519 scalar multiplication (RFC 7748 §5).
71 *
72 * A caller sets the members a call takes, invokes it through ::Curve25519 with the bytes it runs out of,
73 * and reads the outcome off the same handle. How those bytes are carved is this module's and is never
74 * named here.
75 *
76 * Curve25519.x25519_base_args.scalar = ephemeral_priv;
77 * Curve25519.x25519_base_args.out = our_share;
78 * Curve25519.x25519_base(work);
79 * Curve25519.x25519_args.scalar = ephemeral_priv;
80 * Curve25519.x25519_args.point = peer_share;
81 * Curve25519.x25519_args.out = shared_secret;
82 * Curve25519.x25519(work);
83 *
84 * @var Curve25519Ns::x25519_args the scalar and the point one X25519 runs over
85 * @var Curve25519Ns::x25519_base_args the scalar one X25519 against the standard base point runs over
86 * @var Curve25519Ns::ok a call's true/false outcome
87 * @var Curve25519Ns::x25519 out = scalar * point, the Montgomery ladder of RFC 7748 §5
88 * @var Curve25519Ns::x25519_base out = scalar * G, the same ladder against u = 9
89 *
90 * Both entries clamp the scalar per RFC 7748 §5 and are constant-time in it: the ladder swaps
91 * conditionally rather than branching on a scalar bit.
92 *
93 * @c work is PROTOCORE_CURVE25519_BORROW secure bytes the CALLER took, at an address it knows. It
94 * is not held past the call, so nothing here aliases it. The caller releases it,
95 * and the pool wipes on release; this module neither takes it, holds it, releases it, nor wipes it. The
96 * clamped private scalar and every ladder intermediate live in those bytes and nowhere else, so none of
97 * them reaches BSS or the stack. Two key exchanges running at once are two borrows and never collide.
98 *
99 * No storage member and no context: a caller sets operands and reads @ref Curve25519Ns::ok, and that is
100 * all the surface there is.
101 */
102typedef struct
103{
104 Curve25519X25519Args x25519_args;
105 Curve25519X25519BaseArgs x25519_base_args;
106 proto_bool ok;
107} Curve25519Vars;
108
109/** @brief The operands and the outcome. */
110extern Curve25519Vars Curve25519V;
111
112/** @brief The entries. */
113typedef struct
114{
115 void (*const x25519)(uint8_t *work);
116 void (*const x25519_base)(uint8_t *work);
117} Curve25519Ns;
118
119// What the table binds, defined once in the .c and taking one parameter each: everything
120// else an entry needs is an operand in Curve25519V or a region of the borrow at a fixed offset.
121void protocore_curve25519_x25519(uint8_t *work);
122void protocore_curve25519_x25519_base(uint8_t *work);
123
124// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
125// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
126// `Curve25519.x25519(work)` resolves to a named function and becomes a DIRECT call. An extern table
127// leaves the call indirect and the symbol live at every level, -O2 -flto included.
128static const Curve25519Ns Curve25519 __attribute__((unused)) = {
129 .x25519 = protocore_curve25519_x25519,
130 .x25519_base = protocore_curve25519_x25519_base,
131};
132
134
135#endif // PROTOCORE_ENABLE_CURVE25519
136
137#endif // PROTOCORE_CURVE25519_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97