DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ssh_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 ssh_poly1305.cpp
6 * @brief Poly1305 (RFC 8439) - implementation. See ssh_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
16
17// Poly1305 is a hot, pure-integer MAC (the other half of chacha20-poly1305). Like ChaCha it has no vector
18// path on the S3 and runs materially faster than the framework -Os; it is constant-time by structure
19// (the final reduction is branchless), so a higher level for this TU is side-channel safe. Byte-exact.
20// See the caveats in crypto_opt.h and the ChaCha note in ssh_chacha20.cpp.
22
23namespace
24{
25uint32_t rd_le32(const uint8_t *p)
26{
27 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
28}
29void wr_le32(uint8_t *p, uint32_t v)
30{
31 p[0] = (uint8_t)v;
32 p[1] = (uint8_t)(v >> 8);
33 p[2] = (uint8_t)(v >> 16);
34 p[3] = (uint8_t)(v >> 24);
35}
36
37// Absorb one 16-byte block into h: h = (h + block) * r mod (2^130 - 5). hibit is 2^24 for a full
38// block (the implicit high 1 bit at position 128) or 0 for the padded final block.
39void poly_block(uint32_t h[5], const uint32_t r[5], const uint32_t sr[5], const uint8_t blk[16], uint32_t hibit)
40{
41 uint32_t t0 = rd_le32(blk + 0), t1 = rd_le32(blk + 4), t2 = rd_le32(blk + 8), t3 = rd_le32(blk + 12);
42 h[0] += t0 & 0x3ffffff;
43 h[1] += ((t0 >> 26) | (t1 << 6)) & 0x3ffffff;
44 h[2] += ((t1 >> 20) | (t2 << 12)) & 0x3ffffff;
45 h[3] += ((t2 >> 14) | (t3 << 18)) & 0x3ffffff;
46 h[4] += (t3 >> 8) | hibit;
47
48 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] +
49 (uint64_t)h[4] * sr[1];
50 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] +
51 (uint64_t)h[4] * sr[2];
52 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] +
53 (uint64_t)h[4] * sr[3];
54 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] +
55 (uint64_t)h[4] * sr[4];
56 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] +
57 (uint64_t)h[4] * r[0];
58
59 uint32_t c;
60 c = (uint32_t)(d0 >> 26);
61 h[0] = (uint32_t)d0 & 0x3ffffff;
62 d1 += c;
63 c = (uint32_t)(d1 >> 26);
64 h[1] = (uint32_t)d1 & 0x3ffffff;
65 d2 += c;
66 c = (uint32_t)(d2 >> 26);
67 h[2] = (uint32_t)d2 & 0x3ffffff;
68 d3 += c;
69 c = (uint32_t)(d3 >> 26);
70 h[3] = (uint32_t)d3 & 0x3ffffff;
71 d4 += c;
72 c = (uint32_t)(d4 >> 26);
73 h[4] = (uint32_t)d4 & 0x3ffffff;
74 h[0] += c * 5;
75 c = h[0] >> 26;
76 h[0] &= 0x3ffffff;
77 h[1] += c;
78}
79} // namespace
80
81void ssh_poly1305(uint8_t tag[SSH_POLY1305_TAG_LEN], const uint8_t *msg, size_t len,
82 const uint8_t key[SSH_POLY1305_KEY_LEN])
83{
84 uint32_t t0 = rd_le32(key + 0), t1 = rd_le32(key + 4), t2 = rd_le32(key + 8), t3 = rd_le32(key + 12);
85 // Clamp r (RFC 8439 sec 2.5) folded into the limb split.
86 uint32_t r[5];
87 r[0] = t0 & 0x3ffffff;
88 r[1] = ((t0 >> 26) | (t1 << 6)) & 0x3ffff03;
89 r[2] = ((t1 >> 20) | (t2 << 12)) & 0x3ffc0ff;
90 r[3] = ((t2 >> 14) | (t3 << 18)) & 0x3f03fff;
91 r[4] = (t3 >> 8) & 0x00fffff;
92 uint32_t sr[5] = {0, r[1] * 5, r[2] * 5, r[3] * 5, r[4] * 5};
93 uint32_t h[5] = {0, 0, 0, 0, 0};
94
95 while (len >= 16)
96 {
97 poly_block(h, r, sr, msg, 1u << 24);
98 msg += 16;
99 len -= 16;
100 }
101 if (len)
102 {
103 uint8_t buf[16] = {0};
104 for (size_t i = 0; i < len; i++)
105 buf[i] = msg[i];
106 buf[len] = 1; // the message-terminating high bit for the partial block
107 poly_block(h, r, sr, buf, 0);
108 }
109
110 // Fully carry h.
111 uint32_t c;
112 c = h[1] >> 26;
113 h[1] &= 0x3ffffff;
114 h[2] += c;
115 c = h[2] >> 26;
116 h[2] &= 0x3ffffff;
117 h[3] += c;
118 c = h[3] >> 26;
119 h[3] &= 0x3ffffff;
120 h[4] += c;
121 c = h[4] >> 26;
122 h[4] &= 0x3ffffff;
123 h[0] += c * 5;
124 c = h[0] >> 26;
125 h[0] &= 0x3ffffff;
126 h[1] += c;
127
128 // Compute h + -p (i.e. h - (2^130 - 5)).
129 uint32_t g0 = h[0] + 5;
130 c = g0 >> 26;
131 g0 &= 0x3ffffff;
132 uint32_t g1 = h[1] + c;
133 c = g1 >> 26;
134 g1 &= 0x3ffffff;
135 uint32_t g2 = h[2] + c;
136 c = g2 >> 26;
137 g2 &= 0x3ffffff;
138 uint32_t g3 = h[3] + c;
139 c = g3 >> 26;
140 g3 &= 0x3ffffff;
141 uint32_t g4 = h[4] + c - (1u << 26);
142
143 // Select h if h < p, else h + -p; branch-free.
144 uint32_t mask = (g4 >> 31) - 1; // all-ones when g4 has no borrow (h >= p) -> pick g
145 g0 &= mask;
146 g1 &= mask;
147 g2 &= mask;
148 g3 &= mask;
149 g4 &= mask;
150 mask = ~mask;
151 h[0] = (h[0] & mask) | g0;
152 h[1] = (h[1] & mask) | g1;
153 h[2] = (h[2] & mask) | g2;
154 h[3] = (h[3] & mask) | g3;
155 h[4] = (h[4] & mask) | g4;
156
157 // Reassemble h into four 32-bit words (h mod 2^128).
158 uint32_t f0 = (h[0]) | (h[1] << 26);
159 uint32_t f1 = (h[1] >> 6) | (h[2] << 20);
160 uint32_t f2 = (h[2] >> 12) | (h[3] << 14);
161 uint32_t f3 = (h[3] >> 18) | (h[4] << 8);
162
163 // tag = (h + s) mod 2^128, where s = key[16..32].
164 uint64_t f = (uint64_t)f0 + rd_le32(key + 16);
165 f0 = (uint32_t)f;
166 f = (uint64_t)f1 + rd_le32(key + 20) + (f >> 32);
167 f1 = (uint32_t)f;
168 f = (uint64_t)f2 + rd_le32(key + 24) + (f >> 32);
169 f2 = (uint32_t)f;
170 f = (uint64_t)f3 + rd_le32(key + 28) + (f >> 32);
171 f3 = (uint32_t)f;
172
173 wr_le32(tag + 0, f0);
174 wr_le32(tag + 4, f1);
175 wr_le32(tag + 8, f2);
176 wr_le32(tag + 12, f3);
177}
Per-translation-unit optimization override for hot, pure-integer crypto.
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)
uint32_t rd_le32(const uint8_t *p)
void wr_le32(uint8_t *p, uint32_t v)
void ssh_poly1305(uint8_t tag[SSH_POLY1305_TAG_LEN], const uint8_t *msg, size_t len, const uint8_t key[SSH_POLY1305_KEY_LEN])
Compute the 16-byte Poly1305 tag over msg under the 32-byte one-time key.
Poly1305 one-time authenticator (D. J. Bernstein; RFC 8439 Section 2.5).
#define SSH_POLY1305_TAG_LEN
#define SSH_POLY1305_KEY_LEN