ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
bignum.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 bignum.h
6 * @brief 2048-bit big-integer arithmetic for DH-group14 and RSA-2048.
7 *
8 * A protocore_bignum is a fixed-width 2048-bit unsigned integer held as 64 little-endian 32-bit limbs,
9 * so its size is the compile-time constant 256 and a DH scalar and an RSA key fragment are the same
10 * type. The entries below read big-endian bytes in and write them back out, order two values, test one
11 * for zero, check a received DH public value against RFC 4253 §8, and run the group-14 modular
12 * exponentiation on whichever backend the vendor linked.
13 *
14 * @author Douglas Quigg (dstroy0)
15 * @date 2026
16 */
17
18#ifndef PROTOCORE_BIGNUM_H
19#define PROTOCORE_BIGNUM_H
20
21#include "protocore_config.h" // the entry point: protocore_types.h for the widths
22
23#if PROTOCORE_ENABLE_BIGNUM
24
26
27// ---------------------------------------------------------------------------
28// Fixed-width 2048-bit integer
29// ---------------------------------------------------------------------------
30
31/** @brief Number of 32-bit limbs in a 2048-bit integer. */
32#define PROTOCORE_BN_LIMBS 64
33
34/**
35 * @brief A 2048-bit unsigned integer stored as 64 little-endian 32-bit limbs.
36 *
37 * d[0] = least significant 32 bits.
38 * d[63] = most significant 32 bits.
39 */
40typedef struct
41{
42 uint32_t d[PROTOCORE_BN_LIMBS]; ///< 256 bytes of magnitude, little-endian limbs.
43} protocore_bignum;
44
45// ---------------------------------------------------------------------------
46// Group-14 prime constant (exposed for key-derivation and validation)
47// ---------------------------------------------------------------------------
48
49/** @brief The RFC 3526 MODP group-14 prime (2048-bit). */
50extern const protocore_bignum group14_p;
51
52/** @brief Generator for group-14: g = 2. */
53extern const protocore_bignum group14_g;
54
55// PROTOCORE_BIGNUM_BORROW - the bytes a bignum call runs out of - is stated in protocore_config.h,
56// which sums it into the secure arena. A caller takes them once and passes the pointer to every call.
57
58// ---------------------------------------------------------------------------
59// Backend-facing
60// ---------------------------------------------------------------------------
61//
62// bn_expmod_group14() is DECLARED here and DEFINED by exactly one backend under test/core_setup/,
63// chosen by the vendor's PROTOCORE_HAS_HW_BIGNUM. There is no weak default: link no backend and this is an
64// undefined reference; link two and it is a duplicate definition. Software crypto is a legitimate
65// choice - on some parts the only one - but it is always a stated one, never a fallback.
66
67/** @brief Compare two @p n-limb magnitudes: -1, 0 or 1. Shared with the backends. */
68int bn_cmp_raw(const uint32_t *a, const uint32_t *b, int n);
69
70void bn_expmod_group14(protocore_bignum *out, const protocore_bignum *base, const protocore_bignum *exp);
71
72/** @brief The big-endian bytes read into a bignum. */
73typedef struct
74{
75 protocore_bignum *out; ///< destination
76 const uint8_t *bytes; ///< big-endian source bytes
77 size_t len; ///< how many; a shorter source zeroes the top limbs, a longer one keeps its low 256
78} BignumFromBytesArgs;
79
80/** @brief Where a bignum lands as big-endian bytes. */
81typedef struct
82{
83 uint8_t *bytes; ///< exactly 256 bytes
84 const protocore_bignum *in; ///< the value written
85} BignumToBytesArgs;
86
87/** @brief The two values a compare orders. */
88typedef struct
89{
90 const protocore_bignum *a; ///< left
91 const protocore_bignum *b; ///< right
92} BignumCmpArgs;
93
94/** @brief The two magnitudes a raw compare orders. */
95typedef struct
96{
97 const uint32_t *a; ///< left limbs, little-endian
98 const uint32_t *b; ///< right limbs, little-endian
99 int n; ///< limbs spanned
100} BignumCmpRawArgs;
101
102/** @brief The value tested for zero. */
103typedef struct
104{
105 const protocore_bignum *a; ///< the value
106} BignumIsZeroArgs;
107
108/** @brief The operands of a group-14 modular exponentiation. */
109typedef struct
110{
111 protocore_bignum *out; ///< base^exp mod group14_p
112 const protocore_bignum *base; ///< the base, 1 < base < p-1
113 const protocore_bignum *exp; ///< the exponent, e.g. the 2048-bit private DH scalar y
114} BignumExpmodArgs;
115
116/** @brief The received DH public value a validation checks. */
117typedef struct
118{
119 const protocore_bignum *v; ///< the received e or f
120} BignumValidateArgs;
121
122/**
123 * @brief 2048-bit big-integer arithmetic (RFC 3526 group-14).
124 *
125 * A caller sets the members a call takes, invokes it through ::Bignum with the bytes it runs out of,
126 * and reads the outcome off the same handle. How those bytes are carved is this module's and is never
127 * named here.
128 *
129 * Bignum.from_bytes_args.out = &e;
130 * Bignum.from_bytes_args.bytes = e_be;
131 * Bignum.from_bytes_args.len = 256;
132 * Bignum.from_bytes(work);
133 * Bignum.validate_args.v = &e;
134 * Bignum.dh_validate(work);
135 * Bignum.expmod_args.out = &K;
136 * Bignum.expmod_args.base = &e;
137 * Bignum.expmod_args.exp = &y;
138 * Bignum.expmod_group14(work);
139 *
140 * @var BignumNs::from_bytes_args the big-endian bytes read into a bignum
141 * @var BignumNs::to_bytes_args where a bignum lands as big-endian bytes
142 * @var BignumNs::cmp_args the two values a compare orders
143 * @var BignumNs::cmp_raw_args the two magnitudes a raw compare orders
144 * @var BignumNs::is_zero_args the value tested for zero
145 * @var BignumNs::expmod_args the operands of a group-14 modular exponentiation
146 * @var BignumNs::validate_args the received DH public value a validation checks
147 * @var BignumNs::ok a call's true/false outcome
148 * @var BignumNs::sign the sign of a - b the last @ref BignumNs::cmp or @ref BignumNs::cmp_raw
149 * left: -1, 0 or 1
150 * @var BignumNs::zero whether the last @ref BignumNs::is_zero found every limb zero
151 * @var BignumNs::from_bytes read a big-endian byte array into a bignum
152 * @var BignumNs::to_bytes write a bignum as 256 big-endian bytes
153 * @var BignumNs::cmp order two bignums over all 64 limbs
154 * @var BignumNs::cmp_raw order two magnitudes over the stated limb count
155 * @var BignumNs::is_zero test every limb for zero
156 * @var BignumNs::expmod_group14 out = base^exp mod group14_p, on the linked backend
157 * @var BignumNs::dh_validate RFC 4253 §8: @ref BignumNs::ok is true when 1 < v < p-1
158 *
159 * @c work is PROTOCORE_BIGNUM_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the
160 * call, so nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes
161 * it, holds it, releases it, nor wipes it. The DH exponent and the shared secret pass through those bytes, so they die
162 * with the release rather than on the stack. Two callers are two borrows and never collide.
163 *
164 * No storage member and no context: a caller sets operands and reads @ref BignumNs::ok, and that is
165 * all the surface there is.
166 */
167typedef struct
168{
169 BignumFromBytesArgs from_bytes_args;
170 BignumToBytesArgs to_bytes_args;
171 BignumCmpArgs cmp_args;
172 BignumCmpRawArgs cmp_raw_args;
173 BignumIsZeroArgs is_zero_args;
174 BignumExpmodArgs expmod_args;
175 BignumValidateArgs validate_args;
176 proto_bool ok;
177 int sign;
178 proto_bool zero;
179} BignumVars;
180
181/** @brief The operands and the outcome. */
182extern BignumVars BignumV;
183
184/** @brief The entries. */
185typedef struct
186{
187 void (*const from_bytes)(uint8_t *work);
188 void (*const to_bytes)(uint8_t *work);
189 void (*const cmp)(uint8_t *work);
190 void (*const cmp_raw)(uint8_t *work);
191 void (*const is_zero)(uint8_t *work);
192 void (*const expmod_group14)(uint8_t *work);
193 void (*const dh_validate)(uint8_t *work);
194} BignumNs;
195
196// What the table binds, defined once in the .c and taking one parameter each: everything
197// else an entry needs is an operand in BignumV or a region of the borrow at a fixed offset.
198void protocore_bignum_from_bytes(uint8_t *work);
199void protocore_bignum_to_bytes(uint8_t *work);
200void protocore_bignum_cmp(uint8_t *work);
201void protocore_bignum_cmp_raw(uint8_t *work);
202void protocore_bignum_is_zero(uint8_t *work);
203void protocore_bignum_expmod_group14(uint8_t *work);
204void protocore_bignum_dh_validate(uint8_t *work);
205
206// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
207// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
208// `Bignum.from_bytes(work)` resolves to a named function and becomes a DIRECT call. An extern table
209// leaves the call indirect and the symbol live at every level, -O2 -flto included.
210static const BignumNs Bignum __attribute__((unused)) = {
211 .from_bytes = protocore_bignum_from_bytes,
212 .to_bytes = protocore_bignum_to_bytes,
213 .cmp = protocore_bignum_cmp,
214 .cmp_raw = protocore_bignum_cmp_raw,
215 .is_zero = protocore_bignum_is_zero,
216 .expmod_group14 = protocore_bignum_expmod_group14,
217 .dh_validate = protocore_bignum_dh_validate,
218};
219
221
222#endif // PROTOCORE_ENABLE_BIGNUM
223
224#endif // PROTOCORE_BIGNUM_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