ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
poly1305.cpp
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 poly1305.cpp
6 * @brief Poly1305 (RFC 8439) - implementation. See pc_poly1305.h.
7 *
8 * poly1305-donna 32-bit: the accumulator h and key part r are held as five 26-bit limbs; each
9 * 16-byte block adds the message limb (with the 2^128 high bit), multiplies by r, and reduces
10 * modulo 2^130 - 5. The final value is fully reduced, conditionally has p subtracted in constant
11 * time, and s is added modulo 2^128.
12 */
13
14#include "crypto/mac/poly1305.h"
15#include "crypto/crypto_opt.h"
16#include "server/mmgr/secure.h" // the secure pool: nested-MAC working state, wiped on release
17#include <string.h>
18
19// Poly1305 is a hot, pure-integer MAC (the other half of chacha20-poly1305). Like ChaCha it has no vector
20// path on the S3 and runs materially faster than the framework -Os; it is constant-time by structure
21// (the final reduction is branchless), so a higher level for this TU is side-channel safe. Byte-exact.
22// See the caveats in crypto_opt.h and the ChaCha note in pc_chacha20.cpp.
24
25namespace
26{
27uint32_t rd_le32(const uint8_t *p)
28{
29 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
30}
31void wr_le32(uint8_t *p, uint32_t v)
32{
33 p[0] = (uint8_t)v;
34 p[1] = (uint8_t)(v >> 8);
35 p[2] = (uint8_t)(v >> 16);
36 p[3] = (uint8_t)(v >> 24);
37}
38
39// Absorb one 16-byte block into h: h = (h + block) * r mod (2^130 - 5). hibit is 2^24 for a full
40// block (the implicit high 1 bit at position 128) or 0 for the padded final block.
41void poly_block(uint32_t h[5], const uint32_t r[5], const uint32_t sr[5], const uint8_t blk[16], uint32_t hibit)
42{
43 uint32_t t0 = rd_le32(blk + 0), t1 = rd_le32(blk + 4), t2 = rd_le32(blk + 8), t3 = rd_le32(blk + 12);
44 h[0] += t0 & 0x3ffffff;
45 h[1] += ((t0 >> 26) | (t1 << 6)) & 0x3ffffff;
46 h[2] += ((t1 >> 20) | (t2 << 12)) & 0x3ffffff;
47 h[3] += ((t2 >> 14) | (t3 << 18)) & 0x3ffffff;
48 h[4] += (t3 >> 8) | hibit;
49
50 uint64_t d0 = (uint64_t)h[0] * r[0] + (uint64_t)h[1] * sr[4] + (uint64_t)h[2] * sr[3] + (uint64_t)h[3] * sr[2] +
51 (uint64_t)h[4] * sr[1];
52 uint64_t d1 = (uint64_t)h[0] * r[1] + (uint64_t)h[1] * r[0] + (uint64_t)h[2] * sr[4] + (uint64_t)h[3] * sr[3] +
53 (uint64_t)h[4] * sr[2];
54 uint64_t d2 = (uint64_t)h[0] * r[2] + (uint64_t)h[1] * r[1] + (uint64_t)h[2] * r[0] + (uint64_t)h[3] * sr[4] +
55 (uint64_t)h[4] * sr[3];
56 uint64_t d3 = (uint64_t)h[0] * r[3] + (uint64_t)h[1] * r[2] + (uint64_t)h[2] * r[1] + (uint64_t)h[3] * r[0] +
57 (uint64_t)h[4] * sr[4];
58 uint64_t d4 = (uint64_t)h[0] * r[4] + (uint64_t)h[1] * r[3] + (uint64_t)h[2] * r[2] + (uint64_t)h[3] * r[1] +
59 (uint64_t)h[4] * r[0];
60
61 uint32_t c;
62 c = (uint32_t)(d0 >> 26);
63 h[0] = (uint32_t)d0 & 0x3ffffff;
64 d1 += c;
65 c = (uint32_t)(d1 >> 26);
66 h[1] = (uint32_t)d1 & 0x3ffffff;
67 d2 += c;
68 c = (uint32_t)(d2 >> 26);
69 h[2] = (uint32_t)d2 & 0x3ffffff;
70 d3 += c;
71 c = (uint32_t)(d3 >> 26);
72 h[3] = (uint32_t)d3 & 0x3ffffff;
73 d4 += c;
74 c = (uint32_t)(d4 >> 26);
75 h[4] = (uint32_t)d4 & 0x3ffffff;
76 h[0] += c * 5;
77 c = h[0] >> 26;
78 h[0] &= 0x3ffffff;
79 h[1] += c;
80}
81
82// Poly1305 working limbs (key part r, its *5 form sr, accumulator h) + the partial-block buffer, in the
83// shared crypto scratch at the poly1305 region (it runs under chachapoly, so cannot share the base span).
85{
86 uint32_t r[5];
87 uint32_t sr[5];
88 uint32_t h[5];
89 uint8_t buf[16];
90};
91static_assert(sizeof(Poly1305Work) <= PC_WORK_POLY1305,
92 "Poly1305Work outgrew PC_WORK_POLY1305 - raise it in protocore_config.h, which derives "
93 "PC_SECURE_ARENA_SIZE from it");
94
95} // namespace
96
97void pc_poly1305(uint8_t tag[PC_POLY1305_TAG_LEN], const uint8_t *msg, size_t len,
98 const uint8_t key[PC_POLY1305_KEY_LEN])
99{
100 // Working limbs + the partial-block buffer are borrowed from the secure pool, never the stack.
101 // No hand-assigned region: poly1305 runs nested under chachapoly, whose own borrow is still live,
102 // so the pool hands this one a different address by construction. SecureScope wipes it on every
103 // exit path, not just the one that falls off the end.
104 SecureBorrow work_b(sizeof(Poly1305Work), alignof(Poly1305Work));
105 const pc_span &work = work_b.span();
106 if (!pc_span_ok(work))
107 {
108 return; // pool exhausted: fail closed rather than tag with a half-built state
109 }
110 Poly1305Work *w = reinterpret_cast<Poly1305Work *>(work.buf);
111 uint32_t *r = w->r;
112 uint32_t *sr = w->sr;
113 uint32_t *h = w->h;
114 uint32_t t0 = rd_le32(key + 0), t1 = rd_le32(key + 4), t2 = rd_le32(key + 8), t3 = rd_le32(key + 12);
115 // Clamp r (RFC 8439 sec 2.5) folded into the limb split.
116 r[0] = t0 & 0x3ffffff;
117 r[1] = ((t0 >> 26) | (t1 << 6)) & 0x3ffff03;
118 r[2] = ((t1 >> 20) | (t2 << 12)) & 0x3ffc0ff;
119 r[3] = ((t2 >> 14) | (t3 << 18)) & 0x3f03fff;
120 r[4] = (t3 >> 8) & 0x00fffff;
121 sr[0] = 0;
122 sr[1] = r[1] * 5;
123 sr[2] = r[2] * 5;
124 sr[3] = r[3] * 5;
125 sr[4] = r[4] * 5;
126 h[0] = 0;
127 h[1] = 0;
128 h[2] = 0;
129 h[3] = 0;
130 h[4] = 0;
131
132 while (len >= 16)
133 {
134 poly_block(h, r, sr, msg, 1u << 24);
135 msg += 16;
136 len -= 16;
137 }
138 if (len)
139 {
140 memset(w->buf, 0, 16);
141 for (size_t i = 0; i < len; i++)
142 {
143 w->buf[i] = msg[i];
144 }
145 w->buf[len] = 1; // the message-terminating high bit for the partial block
146 poly_block(h, r, sr, w->buf, 0);
147 }
148
149 // Fully carry h.
150 uint32_t c;
151 c = h[1] >> 26;
152 h[1] &= 0x3ffffff;
153 h[2] += c;
154 c = h[2] >> 26;
155 h[2] &= 0x3ffffff;
156 h[3] += c;
157 c = h[3] >> 26;
158 h[3] &= 0x3ffffff;
159 h[4] += c;
160 c = h[4] >> 26;
161 h[4] &= 0x3ffffff;
162 h[0] += c * 5;
163 c = h[0] >> 26;
164 h[0] &= 0x3ffffff;
165 h[1] += c;
166
167 // Compute h + -p (i.e. h - (2^130 - 5)).
168 uint32_t g0 = h[0] + 5;
169 c = g0 >> 26;
170 g0 &= 0x3ffffff;
171 uint32_t g1 = h[1] + c;
172 c = g1 >> 26;
173 g1 &= 0x3ffffff;
174 uint32_t g2 = h[2] + c;
175 c = g2 >> 26;
176 g2 &= 0x3ffffff;
177 uint32_t g3 = h[3] + c;
178 c = g3 >> 26;
179 g3 &= 0x3ffffff;
180 uint32_t g4 = h[4] + c - (1u << 26);
181
182 // Select h if h < p, else h + -p; branch-free.
183 uint32_t mask = (g4 >> 31) - 1; // all-ones when g4 has no borrow (h >= p) -> pick g
184 g0 &= mask;
185 g1 &= mask;
186 g2 &= mask;
187 g3 &= mask;
188 g4 &= mask;
189 mask = ~mask;
190 h[0] = (h[0] & mask) | g0;
191 h[1] = (h[1] & mask) | g1;
192 h[2] = (h[2] & mask) | g2;
193 h[3] = (h[3] & mask) | g3;
194 h[4] = (h[4] & mask) | g4;
195
196 // Reassemble h into four 32-bit words (h mod 2^128).
197 uint32_t f0 = (h[0]) | (h[1] << 26);
198 uint32_t f1 = (h[1] >> 6) | (h[2] << 20);
199 uint32_t f2 = (h[2] >> 12) | (h[3] << 14);
200 uint32_t f3 = (h[3] >> 18) | (h[4] << 8);
201
202 // tag = (h + s) mod 2^128, where s = key[16..32].
203 uint64_t f = (uint64_t)f0 + rd_le32(key + 16);
204 f0 = (uint32_t)f;
205 f = (uint64_t)f1 + rd_le32(key + 20) + (f >> 32);
206 f1 = (uint32_t)f;
207 f = (uint64_t)f2 + rd_le32(key + 24) + (f >> 32);
208 f2 = (uint32_t)f;
209 f = (uint64_t)f3 + rd_le32(key + 28) + (f >> 32);
210 f3 = (uint32_t)f;
211
212 wr_le32(tag + 0, f0);
213 wr_le32(tag + 4, f1);
214 wr_le32(tag + 8, f2);
215 wr_le32(tag + 12, f3);
216}
A secure borrow whose acquire and release are one call each.
Definition secure.h:153
const pc_span & span() const
The borrowed region; empty if the pool could not satisfy it.
Definition secure.h:161
Per-translation-unit optimization override for hot, pure-integer crypto.
void wr_le32(uint8_t *p, uint32_t v)
Definition poly1305.cpp:31
void poly_block(uint32_t h[5], const uint32_t r[5], const uint32_t sr[5], const uint8_t blk[16], uint32_t hibit)
Definition poly1305.cpp:41
uint32_t rd_le32(const uint8_t *p)
Definition chacha20.cpp:31
void pc_poly1305(uint8_t tag[PC_POLY1305_TAG_LEN], const uint8_t *msg, size_t len, const uint8_t key[PC_POLY1305_KEY_LEN])
Compute the 16-byte Poly1305 tag over msg under the 32-byte one-time key.
Definition poly1305.cpp:97
Poly1305 one-time authenticator (D. J. Bernstein; RFC 8439 Section 2.5).
#define PC_POLY1305_KEY_LEN
Definition poly1305.h:23
#define PC_POLY1305_TAG_LEN
Definition poly1305.h:24
#define PC_WORK_POLY1305
Secure pool accessor - borrows that hold key material.
bool pc_span_ok(const pc_span &s)
True when the span refers to real storage and every write so far has fit.
Definition span.h:114
A writable byte region: the storage, the capacity that belongs to it, and what has been produced into...
Definition span.h:65
uint8_t * buf
first byte, or nullptr when the region could not be obtained
Definition span.h:66