ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
bignum.h
Go to the documentation of this file.
1// 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 * ═══════════════════════════════════════════════════════════════════════════
9 * DESIGN RATIONALE
10 * ═══════════════════════════════════════════════════════════════════════════
11 *
12 * pc_bignum is a fixed-width 2048-bit integer stored as 64 little-endian
13 * 32-bit limbs (d[0] = least significant). Fixed width means:
14 * - Struct size is a compile-time constant: 256 bytes.
15 * - No dynamic allocation - both DH scalars and RSA key fragments fit in
16 * the same type and can live in BSS or on the stack.
17 * - Array indexing is bounds-safe; no VLA or pointer arithmetic hazards.
18 *
19 * On Arduino (ESP32), DH uses mbedtls_mpi from ESP-IDF (heap-allocated,
20 * variable-length bignum), which has hardware-accelerated multiplication.
21 * On native builds, the software Montgomery path is used - correct but
22 * slower (~200 ms for a 2048-bit exponentiation on x86 at test time).
23 * Since DH happens once per connection and ESP32 uses the HW path in
24 * production, the native speed is acceptable for testing.
25 *
26 * ═══════════════════════════════════════════════════════════════════════════
27 * MONTGOMERY MULTIPLICATION
28 * ═══════════════════════════════════════════════════════════════════════════
29 *
30 * For DH-group14 the modulus p ends in ...FFFFFFFF FFFFFFFF (little-endian
31 * d[0]=0xFFFFFFFF). This gives Montgomery parameter:
32 *
33 * p_inv = (-(p mod 2^32))^(-1) mod 2^32
34 * = (-(0xFFFFFFFF))^(-1) mod 2^32
35 * = (0x00000001)^(-1) mod 2^32
36 * = 1
37 *
38 * p_inv = 1 simplifies the inner reduction loop: m_i = t[i] * 1 = t[i].
39 *
40 * Montgomery product: MonPro(a,b) = a·b·R^-1 mod p
41 * where R = 2^2048.
42 *
43 * To compute a·b mod p normally:
44 * 1. Convert a, b to Montgomery form: aR = a·R mod p (= MonPro(a, R²mod p))
45 * 2. Compute MonPro(aR, bR) = a·b·R mod p
46 * 3. Convert back: MonPro(a·b·R, 1) = a·b mod p
47 *
48 * R² mod p is a precomputed 2048-bit constant (see pc_bignum.cpp).
49 *
50 * ═══════════════════════════════════════════════════════════════════════════
51 * SCRATCH BUFFER
52 * ═══════════════════════════════════════════════════════════════════════════
53 *
54 * The Montgomery SOS multiplication needs a 129-word (516-byte) temporary and the
55 * expmod three pc_bignum temporaries (768 bytes). bn_expmod_group14() borrows all of
56 * them as ONE working set from the secure pool (server/mmgr/secure.h), so the layout
57 * is the struct's own field order rather than byte offsets kept in step by hand here.
58 *
59 * These temporaries hold DH private-exponent and shared-secret fragments. They are
60 * wiped when the borrow is released - by the pool, on every exit path. This module
61 * does not perform the wipe and does not need to know that one happens.
62 *
63 * ═══════════════════════════════════════════════════════════════════════════
64 *
65 * @author Douglas Quigg (dstroy0)
66 * @date 2026
67 */
68
69#ifndef PROTOCORE_BIGNUM_H
70#define PROTOCORE_BIGNUM_H
71
72#include "protocore_config.h"
73#include "server/mmgr/secure.h"
74#include <stddef.h>
75#include <stdint.h>
76#include <string.h>
77
78// ---------------------------------------------------------------------------
79// Fixed-width 2048-bit integer
80// ---------------------------------------------------------------------------
81
82/** @brief Number of 32-bit limbs in a 2048-bit integer. */
83#define PC_BN_LIMBS 64
84
85/**
86 * @brief A 2048-bit unsigned integer stored as 64 little-endian 32-bit limbs.
87 *
88 * d[0] = least significant 32 bits.
89 * d[63] = most significant 32 bits.
90 */
92{
93 uint32_t d[PC_BN_LIMBS]; ///< 256 bytes of magnitude, little-endian limbs.
94};
95
96// ---------------------------------------------------------------------------
97// Scratch buffer (defined in pc_bignum.cpp)
98// ---------------------------------------------------------------------------
99
100// bn_expmod_group14() borrows its Montgomery temporaries from the secure pool as one working set; the pool
101// wipes them when the borrow is released. This module names no address and performs no wipe.
102
103// ---------------------------------------------------------------------------
104// Conversion helpers
105// ---------------------------------------------------------------------------
106
107/**
108 * @brief Read a big-endian byte array of @p len bytes into a pc_bignum.
109 *
110 * If @p len < 256 the most-significant limbs are zeroed.
111 * If @p len > 256 only the least-significant 256 bytes are read.
112 *
113 * @param out Destination bignum.
114 * @param bytes Big-endian source bytes.
115 * @param len Number of source bytes (typically 256 for 2048-bit).
116 */
117void bn_from_bytes(pc_bignum *out, const uint8_t *bytes, size_t len);
118
119/**
120 * @brief Write a pc_bignum as a 256-byte big-endian array.
121 *
122 * @param bytes Destination buffer (exactly 256 bytes).
123 * @param in Source bignum.
124 */
125void bn_to_bytes(uint8_t bytes[256], const pc_bignum *in);
126
127// ---------------------------------------------------------------------------
128// Comparison
129// ---------------------------------------------------------------------------
130
131/**
132 * @brief Compare two pc_bignum values.
133 * @return -1 if a < b, 0 if a == b, 1 if a > b.
134 */
135int bn_cmp(const pc_bignum *a, const pc_bignum *b);
136
137/**
138 * @brief Return non-zero if @p a is zero (all limbs zero).
139 */
140int bn_is_zero(const pc_bignum *a);
141
142// ---------------------------------------------------------------------------
143// DH-group14 modular exponentiation
144// ---------------------------------------------------------------------------
145
146/**
147 * @brief Compute out = base^exp mod group14_p.
148 *
149 * Uses Montgomery modular exponentiation with left-to-right binary scan.
150 * Borrows one working set for all temporaries; it is wiped when released.
151 *
152 * On Arduino the computation is delegated to mbedtls_mpi_exp_mod() which
153 * uses hardware multiplication and blinding.
154 *
155 * @param out Result (base^exp mod p, 2048-bit).
156 * @param base Base value; must satisfy 1 < base < p-1.
157 * @param exp Exponent (e.g. the 2048-bit private DH scalar y).
158 */
159// ---------------------------------------------------------------------------
160// Backend-facing
161// ---------------------------------------------------------------------------
162//
163// bn_expmod_group14() is DECLARED here and DEFINED by exactly one backend under board_drivers/,
164// chosen by the vendor's PC_HAS_HW_BIGNUM. There is no weak default: link no backend and this is an
165// undefined reference; link two and it is a duplicate definition. Software crypto is a legitimate
166// choice - on some parts the only one - but it is always a stated one, never a fallback.
167
168/** @brief Compare two @p n-limb magnitudes: -1, 0 or 1. Shared with the backends. */
169int bn_cmp_raw(const uint32_t *a, const uint32_t *b, int n);
170
171void bn_expmod_group14(pc_bignum *out, const pc_bignum *base, const pc_bignum *exp);
172
173/**
174 * @brief Validate a received DH public value.
175 *
176 * RFC 4253 §8: the received value e (or f) must satisfy 1 < e < p-1.
177 * Returns 0 if the value is valid, -1 otherwise.
178 *
179 * @param v Received public DH value.
180 */
181int bn_dh_validate(const pc_bignum *v);
182
183// ---------------------------------------------------------------------------
184// Group-14 prime constant (exposed for key-derivation and validation)
185// ---------------------------------------------------------------------------
186
187/** @brief The RFC 3526 MODP group-14 prime (2048-bit). */
188extern const pc_bignum group14_p;
189
190/** @brief Generator for group-14: g = 2. */
191extern const pc_bignum group14_g;
192
193#endif // PROTOCORE_BIGNUM_H
void bn_to_bytes(uint8_t bytes[256], const pc_bignum *in)
Write a pc_bignum as a 256-byte big-endian array.
Definition bignum.cpp:96
const pc_bignum group14_g
Generator for group-14: g = 2.
Definition bignum.cpp:54
int bn_cmp(const pc_bignum *a, const pc_bignum *b)
Compare two pc_bignum values.
Definition bignum.cpp:108
const pc_bignum group14_p
The RFC 3526 MODP group-14 prime (2048-bit).
Definition bignum.cpp:42
int bn_is_zero(const pc_bignum *a)
Return non-zero if a is zero (all limbs zero).
Definition bignum.cpp:113
#define PC_BN_LIMBS
Number of 32-bit limbs in a 2048-bit integer.
Definition bignum.h:83
void bn_from_bytes(pc_bignum *out, const uint8_t *bytes, size_t len)
Read a big-endian byte array of len bytes into a pc_bignum.
Definition bignum.cpp:84
void bn_expmod_group14(pc_bignum *out, const pc_bignum *base, const pc_bignum *exp)
int bn_dh_validate(const pc_bignum *v)
Validate a received DH public value.
Definition bignum.cpp:125
int bn_cmp_raw(const uint32_t *a, const uint32_t *b, int n)
Compute out = base^exp mod group14_p.
Definition bignum.cpp:64
User-facing configuration for ProtoCore.
Secure pool accessor - borrows that hold key material.
A 2048-bit unsigned integer stored as 64 little-endian 32-bit limbs.
Definition bignum.h:92
uint32_t d[PC_BN_LIMBS]
256 bytes of magnitude, little-endian limbs.
Definition bignum.h:93