Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
ed25519.inc
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | // SHA-512 (FIPS 180-4) and Ed25519 (RFC 8032) for Safe Actions. |
| 2 | // |
| 3 | // The Ed25519 arithmetic follows TweetNaCl (Bernstein, van Gastel, Janssen, |
| 4 | // Lange, Schwabe, Smetsers; public domain): small, constant-time in the secret |
| 5 | // scalar for signing, and easy to audit. Two changes for verification: S must be |
| 6 | // canonical (S < L, RFC 8032 section 5.1.7), and shifts of possibly negative |
| 7 | // values are written as multiplication. Checked against the RFC 8032 vectors |
| 8 | // and against independent implementations in tests/safe_actions_tests.cpp. |
| 9 | |
| 10 | namespace ckey_crypto { |
| 11 | |
| 12 | // ---------------------------------------------------------------- SHA-512 |
| 13 | |
| 14 | static const uint64_t kSha512K[80] = { |
| 15 | 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, |
| 16 | 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, |
| 17 | 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, |
| 18 | 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, |
| 19 | 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, |
| 20 | 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, |
| 21 | 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, |
| 22 | 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, |
| 23 | 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, |
| 24 | 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, |
| 25 | 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, |
| 26 | 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, |
| 27 | 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, |
| 28 | 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, |
| 29 | 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, |
| 30 | 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL}; |
| 31 | |
| 32 | static inline uint64_t ror64(uint64_t x, int n) { return (x >> n) | (x << (64 - n)); } |
| 33 | |
| 34 | struct Sha512 { |
| 35 | uint64_t h[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, |
| 36 | 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL}; |
| 37 | uint8_t buf[128] = {}; |
| 38 | size_t used = 0; |
| 39 | uint64_t total = 0; |
| 40 | |
| 41 | void block(const uint8_t *p) { |
| 42 | uint64_t w[80]; |
| 43 | for (int i = 0; i < 16; ++i) { |
| 44 | w[i] = 0; |
| 45 | for (int j = 0; j < 8; ++j) w[i] = (w[i] << 8) | p[8 * i + j]; |
| 46 | } |
| 47 | for (int i = 16; i < 80; ++i) { |
| 48 | const uint64_t s0 = ror64(w[i - 15], 1) ^ ror64(w[i - 15], 8) ^ (w[i - 15] >> 7); |
| 49 | const uint64_t s1 = ror64(w[i - 2], 19) ^ ror64(w[i - 2], 61) ^ (w[i - 2] >> 6); |
| 50 | w[i] = w[i - 16] + s0 + w[i - 7] + s1; |
| 51 | } |
| 52 | uint64_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], k = h[7]; |
| 53 | for (int i = 0; i < 80; ++i) { |
| 54 | const uint64_t t1 = k + (ror64(e, 14) ^ ror64(e, 18) ^ ror64(e, 41)) + ((e & f) ^ (~e & g)) + kSha512K[i] + w[i]; |
| 55 | const uint64_t t2 = (ror64(a, 28) ^ ror64(a, 34) ^ ror64(a, 39)) + ((a & b) ^ (a & c) ^ (b & c)); |
| 56 | k = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2; |
| 57 | } |
| 58 | h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e; h[5] += f; h[6] += g; h[7] += k; |
| 59 | } |
| 60 | void update(const uint8_t *p, size_t n) { |
| 61 | total += n; |
| 62 | while (n) { |
| 63 | const size_t take = std::min(n, sizeof(buf) - used); |
| 64 | std::memcpy(buf + used, p, take); |
| 65 | used += take; p += take; n -= take; |
| 66 | if (used == sizeof(buf)) { block(buf); used = 0; } |
| 67 | } |
| 68 | } |
| 69 | void finish(uint8_t out[64]) { |
| 70 | const uint64_t bits = total * 8; |
| 71 | const uint8_t one = 0x80; |
| 72 | update(&one, 1); |
| 73 | const uint8_t zero = 0; |
| 74 | while (used != 112) update(&zero, 1); |
| 75 | uint8_t len[16] = {}; |
| 76 | for (int i = 0; i < 8; ++i) len[15 - i] = static_cast<uint8_t>(bits >> (8 * i)); |
| 77 | update(len, 16); |
| 78 | for (int i = 0; i < 8; ++i) |
| 79 | for (int j = 0; j < 8; ++j) out[8 * i + j] = static_cast<uint8_t>(h[i] >> (56 - 8 * j)); |
| 80 | } |
| 81 | }; |
| 82 | |
| 83 | static void sha512(uint8_t out[64], const uint8_t *a, size_t an, const uint8_t *b = nullptr, size_t bn = 0, |
| 84 | const uint8_t *c = nullptr, size_t cn = 0) { |
| 85 | Sha512 s; |
| 86 | s.update(a, an); |
| 87 | if (b) s.update(b, bn); |
| 88 | if (c) s.update(c, cn); |
| 89 | s.finish(out); |
| 90 | } |
| 91 | |
| 92 | // ---------------------------------------------------------------- field arithmetic mod 2^255 - 19 |
| 93 | |
| 94 | typedef int64_t gf[16]; |
| 95 | static const gf gf0 = {0}; |
| 96 | static const gf gf1 = {1}; |
| 97 | static const gf kD = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203}; |
| 98 | static const gf kD2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406}; |
| 99 | static const gf kX = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169}; |
| 100 | static const gf kY = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666}; |
| 101 | static const gf kI = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83}; |
| 102 | |
| 103 | static void set25519(gf r, const gf a) { for (int i = 0; i < 16; ++i) r[i] = a[i]; } |
| 104 | |
| 105 | static void car25519(gf o) { |
| 106 | for (int i = 0; i < 16; ++i) { |
| 107 | o[i] += (int64_t{1} << 16); |
| 108 | const int64_t c = o[i] >> 16; // arithmetic shift (C++20) |
| 109 | o[(i + 1) * (i < 15)] += c - 1 + 37 * (c - 1) * (i == 15); |
| 110 | o[i] -= c * 65536; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | static void sel25519(gf p, gf q, int b) { |
| 115 | const int64_t c = ~(int64_t(b) - 1); |
| 116 | for (int i = 0; i < 16; ++i) { |
| 117 | const int64_t t = c & (p[i] ^ q[i]); |
| 118 | p[i] ^= t; |
| 119 | q[i] ^= t; |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | static void pack25519(uint8_t *o, const gf n) { |
| 124 | gf m, t; |
| 125 | set25519(t, n); |
| 126 | car25519(t); car25519(t); car25519(t); |
| 127 | for (int j = 0; j < 2; ++j) { |
| 128 | m[0] = t[0] - 0xffed; |
| 129 | for (int i = 1; i < 15; ++i) { |
| 130 | m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); |
| 131 | m[i - 1] &= 0xffff; |
| 132 | } |
| 133 | m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); |
| 134 | const int b = static_cast<int>((m[15] >> 16) & 1); |
| 135 | m[14] &= 0xffff; |
| 136 | sel25519(t, m, 1 - b); |
| 137 | } |
| 138 | for (int i = 0; i < 16; ++i) { |
| 139 | o[2 * i] = static_cast<uint8_t>(t[i] & 0xff); |
| 140 | o[2 * i + 1] = static_cast<uint8_t>(t[i] >> 8); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | static int verify32(const uint8_t *x, const uint8_t *y) { |
| 145 | unsigned d = 0; |
| 146 | for (int i = 0; i < 32; ++i) d |= x[i] ^ y[i]; |
| 147 | return (1 & ((d - 1) >> 8)) - 1; // 0 when equal, -1 otherwise |
| 148 | } |
| 149 | |
| 150 | static int neq25519(const gf a, const gf b) { |
| 151 | uint8_t c[32], d[32]; |
| 152 | pack25519(c, a); |
| 153 | pack25519(d, b); |
| 154 | return verify32(c, d); |
| 155 | } |
| 156 | |
| 157 | static uint8_t par25519(const gf a) { |
| 158 | uint8_t d[32]; |
| 159 | pack25519(d, a); |
| 160 | return d[0] & 1; |
| 161 | } |
| 162 | |
| 163 | static void unpack25519(gf o, const uint8_t *n) { |
| 164 | for (int i = 0; i < 16; ++i) o[i] = n[2 * i] + (int64_t(n[2 * i + 1]) << 8); |
| 165 | o[15] &= 0x7fff; |
| 166 | } |
| 167 | |
| 168 | static void fadd(gf o, const gf a, const gf b) { for (int i = 0; i < 16; ++i) o[i] = a[i] + b[i]; } |
| 169 | static void fsub(gf o, const gf a, const gf b) { for (int i = 0; i < 16; ++i) o[i] = a[i] - b[i]; } |
| 170 | |
| 171 | static void fmul(gf o, const gf a, const gf b) { |
| 172 | int64_t t[31] = {0}; |
| 173 | for (int i = 0; i < 16; ++i) |
| 174 | for (int j = 0; j < 16; ++j) t[i + j] += a[i] * b[j]; |
| 175 | for (int i = 0; i < 15; ++i) t[i] += 38 * t[i + 16]; |
| 176 | for (int i = 0; i < 16; ++i) o[i] = t[i]; |
| 177 | car25519(o); |
| 178 | car25519(o); |
| 179 | } |
| 180 | |
| 181 | static void fsq(gf o, const gf a) { fmul(o, a, a); } |
| 182 | |
| 183 | static void inv25519(gf o, const gf in) { |
| 184 | gf c; |
| 185 | set25519(c, in); |
| 186 | for (int a = 253; a >= 0; --a) { |
| 187 | fsq(c, c); |
| 188 | if (a != 2 && a != 4) fmul(c, c, in); |
| 189 | } |
| 190 | set25519(o, c); |
| 191 | } |
| 192 | |
| 193 | static void pow2523(gf o, const gf in) { |
| 194 | gf c; |
| 195 | set25519(c, in); |
| 196 | for (int a = 250; a >= 0; --a) { |
| 197 | fsq(c, c); |
| 198 | if (a != 1) fmul(c, c, in); |
| 199 | } |
| 200 | set25519(o, c); |
| 201 | } |
| 202 | |
| 203 | // ---------------------------------------------------------------- the Edwards curve |
| 204 | |
| 205 | static void point_add(gf p[4], gf q[4]) { |
| 206 | gf a, b, c, d, t, e, f, g, h; |
| 207 | fsub(a, p[1], p[0]); |
| 208 | fsub(t, q[1], q[0]); |
| 209 | fmul(a, a, t); |
| 210 | fadd(b, p[0], p[1]); |
| 211 | fadd(t, q[0], q[1]); |
| 212 | fmul(b, b, t); |
| 213 | fmul(c, p[3], q[3]); |
| 214 | fmul(c, c, kD2); |
| 215 | fmul(d, p[2], q[2]); |
| 216 | fadd(d, d, d); |
| 217 | fsub(e, b, a); |
| 218 | fsub(f, d, c); |
| 219 | fadd(g, d, c); |
| 220 | fadd(h, b, a); |
| 221 | fmul(p[0], e, f); |
| 222 | fmul(p[1], h, g); |
| 223 | fmul(p[2], g, f); |
| 224 | fmul(p[3], e, h); |
| 225 | } |
| 226 | |
| 227 | static void cswap(gf p[4], gf q[4], uint8_t b) { |
| 228 | for (int i = 0; i < 4; ++i) sel25519(p[i], q[i], b); |
| 229 | } |
| 230 | |
| 231 | static void pack_point(uint8_t *r, gf p[4]) { |
| 232 | gf tx, ty, zi; |
| 233 | inv25519(zi, p[2]); |
| 234 | fmul(tx, p[0], zi); |
| 235 | fmul(ty, p[1], zi); |
| 236 | pack25519(r, ty); |
| 237 | r[31] ^= static_cast<uint8_t>(par25519(tx) << 7); |
| 238 | } |
| 239 | |
| 240 | static void scalarmult(gf p[4], gf q[4], const uint8_t *s) { |
| 241 | set25519(p[0], gf0); |
| 242 | set25519(p[1], gf1); |
| 243 | set25519(p[2], gf1); |
| 244 | set25519(p[3], gf0); |
| 245 | for (int i = 255; i >= 0; --i) { |
| 246 | const uint8_t b = (s[i / 8] >> (i & 7)) & 1; |
| 247 | cswap(p, q, b); |
| 248 | point_add(q, p); |
| 249 | point_add(p, p); |
| 250 | cswap(p, q, b); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | static void scalarbase(gf p[4], const uint8_t *s) { |
| 255 | gf q[4]; |
| 256 | set25519(q[0], kX); |
| 257 | set25519(q[1], kY); |
| 258 | set25519(q[2], gf1); |
| 259 | fmul(q[3], kX, kY); |
| 260 | scalarmult(p, q, s); |
| 261 | } |
| 262 | |
| 263 | static const int64_t kL[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, |
| 264 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10}; |
| 265 | |
| 266 | static void modL(uint8_t *r, int64_t x[64]) { |
| 267 | for (int i = 63; i >= 32; --i) { |
| 268 | int64_t carry = 0; |
| 269 | int j; |
| 270 | for (j = i - 32; j < i - 12; ++j) { |
| 271 | x[j] += carry - 16 * x[i] * kL[j - (i - 32)]; |
| 272 | carry = (x[j] + 128) >> 8; |
| 273 | x[j] -= carry * 256; |
| 274 | } |
| 275 | x[j] += carry; |
| 276 | x[i] = 0; |
| 277 | } |
| 278 | int64_t carry = 0; |
| 279 | for (int j = 0; j < 32; ++j) { |
| 280 | x[j] += carry - (x[31] >> 4) * kL[j]; |
| 281 | carry = x[j] >> 8; |
| 282 | x[j] &= 255; |
| 283 | } |
| 284 | for (int j = 0; j < 32; ++j) x[j] -= carry * kL[j]; |
| 285 | for (int i = 0; i < 32; ++i) { |
| 286 | x[i + 1] += x[i] >> 8; |
| 287 | r[i] = static_cast<uint8_t>(x[i] & 255); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | static void reduce64(uint8_t *r) { |
| 292 | int64_t x[64]; |
| 293 | for (int i = 0; i < 64; ++i) x[i] = r[i]; |
| 294 | for (int i = 0; i < 64; ++i) r[i] = 0; |
| 295 | modL(r, x); |
| 296 | } |
| 297 | |
| 298 | static int unpackneg(gf r[4], const uint8_t p[32]) { |
| 299 | gf t, chk, num, den, den2, den4, den6; |
| 300 | set25519(r[2], gf1); |
| 301 | unpack25519(r[1], p); |
| 302 | fsq(num, r[1]); |
| 303 | fmul(den, num, kD); |
| 304 | fsub(num, num, r[2]); |
| 305 | fadd(den, r[2], den); |
| 306 | fsq(den2, den); |
| 307 | fsq(den4, den2); |
| 308 | fmul(den6, den4, den2); |
| 309 | fmul(t, den6, num); |
| 310 | fmul(t, t, den); |
| 311 | pow2523(t, t); |
| 312 | fmul(t, t, num); |
| 313 | fmul(t, t, den); |
| 314 | fmul(t, t, den); |
| 315 | fmul(r[0], t, den); |
| 316 | fsq(chk, r[0]); |
| 317 | fmul(chk, chk, den); |
| 318 | if (neq25519(chk, num)) fmul(r[0], r[0], kI); |
| 319 | fsq(chk, r[0]); |
| 320 | fmul(chk, chk, den); |
| 321 | if (neq25519(chk, num)) return -1; |
| 322 | if (par25519(r[0]) == (p[31] >> 7)) fsub(r[0], gf0, r[0]); |
| 323 | fmul(r[3], r[0], r[1]); |
| 324 | return 0; |
| 325 | } |
| 326 | |
| 327 | /** The public key for a 32-byte seed. */ |
| 328 | static void ed25519_public_key(uint8_t pk[32], const uint8_t seed[32]) { |
| 329 | uint8_t d[64]; |
| 330 | gf p[4]; |
| 331 | sha512(d, seed, 32); |
| 332 | d[0] &= 248; |
| 333 | d[31] &= 127; |
| 334 | d[31] |= 64; |
| 335 | scalarbase(p, d); |
| 336 | pack_point(pk, p); |
| 337 | } |
| 338 | |
| 339 | /** A detached RFC 8032 signature of msg with a 32-byte seed. */ |
| 340 | static void ed25519_sign(uint8_t sig[64], const uint8_t *msg, size_t n, const uint8_t seed[32]) { |
| 341 | uint8_t d[64], r[64], h[64], pk[32]; |
| 342 | gf p[4]; |
| 343 | sha512(d, seed, 32); |
| 344 | d[0] &= 248; |
| 345 | d[31] &= 127; |
| 346 | d[31] |= 64; |
| 347 | ed25519_public_key(pk, seed); |
| 348 | sha512(r, d + 32, 32, msg, n); |
| 349 | reduce64(r); |
| 350 | scalarbase(p, r); |
| 351 | pack_point(sig, p); |
| 352 | sha512(h, sig, 32, pk, 32, msg, n); |
| 353 | reduce64(h); |
| 354 | int64_t x[64] = {0}; |
| 355 | for (int i = 0; i < 32; ++i) x[i] = r[i]; |
| 356 | for (int i = 0; i < 32; ++i) |
| 357 | for (int j = 0; j < 32; ++j) x[i + j] += int64_t(h[i]) * int64_t(d[j]); |
| 358 | modL(sig + 32, x); |
| 359 | } |
| 360 | |
| 361 | /** True when S < L, the group order (little-endian comparison). */ |
| 362 | static bool scalar_canonical(const uint8_t s[32]) { |
| 363 | for (int i = 31; i >= 0; --i) { |
| 364 | if (s[i] < kL[i]) return true; |
| 365 | if (s[i] > kL[i]) return false; |
| 366 | } |
| 367 | return false; // equal to L |
| 368 | } |
| 369 | |
| 370 | /** Verifies a detached signature. */ |
| 371 | static bool ed25519_verify(const uint8_t sig[64], const uint8_t *msg, size_t n, const uint8_t pk[32]) { |
| 372 | if (!scalar_canonical(sig + 32)) return false; |
| 373 | gf p[4], q[4]; |
| 374 | if (unpackneg(q, pk)) return false; |
| 375 | uint8_t h[64], t[32]; |
| 376 | sha512(h, sig, 32, pk, 32, msg, n); |
| 377 | reduce64(h); |
| 378 | scalarmult(p, q, h); |
| 379 | scalarbase(q, sig + 32); |
| 380 | point_add(p, q); |
| 381 | pack_point(t, p); |
| 382 | return verify32(sig, t) == 0; |
| 383 | } |
| 384 | |
| 385 | } // namespace ckey_crypto |