diff --git a/runtime/src/crypto.c b/runtime/src/crypto.c index 3f5d9ae..da21913 100644 --- a/runtime/src/crypto.c +++ b/runtime/src/crypto.c @@ -892,6 +892,176 @@ int wo_aes_gcm_open(const uint8_t *key, size_t keylen, const uint8_t nonce[12], tag); } +/* ---- X25519 (rv2 9 phase C, RFC 7748) ---------------------------------- + * Montgomery-ladder scalar multiplication over Curve25519, constant-time + * (mask-based conditional swap, no data-dependent branches). Field arithmetic + * is the radix-2^51 representation with 128-bit intermediate products + * (curve25519-donna-c64, public domain). Internal C; the consumer is the TLS + * ECDHE handshake. Vectors: RFC 7748 §5.2 in test_crypto.c. */ +typedef uint64_t felem[5]; +typedef unsigned __int128 u128; +#define FE_MASK 0x7ffffffffffffULL + +static uint64_t ld64(const uint8_t *b) { + uint64_t r = 0; + for (int i = 0; i < 8; i++) r |= (uint64_t)b[i] << (8 * i); + return r; +} +static void st64(uint8_t *b, uint64_t v) { + for (int i = 0; i < 8; i++) b[i] = (uint8_t)(v >> (8 * i)); +} + +static void fexpand(felem out, const uint8_t *in) { + out[0] = ld64(in) & FE_MASK; + out[1] = (ld64(in + 6) >> 3) & FE_MASK; + out[2] = (ld64(in + 12) >> 6) & FE_MASK; + out[3] = (ld64(in + 19) >> 1) & FE_MASK; + out[4] = (ld64(in + 24) >> 12) & FE_MASK; +} + +static void fcontract(uint8_t *out, const felem in) { + felem h; + for (int i = 0; i < 5; i++) h[i] = in[i]; + for (int r = 0; r < 3; r++) { /* weak-reduce a few times */ + uint64_t c; + c = h[0] >> 51; h[0] &= FE_MASK; h[1] += c; + c = h[1] >> 51; h[1] &= FE_MASK; h[2] += c; + c = h[2] >> 51; h[2] &= FE_MASK; h[3] += c; + c = h[3] >> 51; h[3] &= FE_MASK; h[4] += c; + c = h[4] >> 51; h[4] &= FE_MASK; h[0] += 19 * c; + } + /* q = 1 iff h >= p = 2^255-19 */ + uint64_t q = (h[0] + 19) >> 51; + q = (h[1] + q) >> 51; q = (h[2] + q) >> 51; + q = (h[3] + q) >> 51; q = (h[4] + q) >> 51; + h[0] += 19 * q; + h[1] += h[0] >> 51; h[0] &= FE_MASK; + h[2] += h[1] >> 51; h[1] &= FE_MASK; + h[3] += h[2] >> 51; h[2] &= FE_MASK; + h[4] += h[3] >> 51; h[3] &= FE_MASK; + h[4] &= FE_MASK; + st64(out, h[0] | (h[1] << 51)); + st64(out + 8, (h[1] >> 13) | (h[2] << 38)); + st64(out + 16, (h[2] >> 26) | (h[3] << 25)); + st64(out + 24, (h[3] >> 39) | (h[4] << 12)); +} + +static void fsum(felem out, const felem a, const felem b) { + for (int i = 0; i < 5; i++) out[i] = a[i] + b[i]; +} +static void fdiff(felem out, const felem a, const felem b) { /* out = a - b */ + static const uint64_t t54m152 = (1ULL << 54) - 152, t54m8 = (1ULL << 54) - 8; + out[0] = a[0] + t54m152 - b[0]; + out[1] = a[1] + t54m8 - b[1]; + out[2] = a[2] + t54m8 - b[2]; + out[3] = a[3] + t54m8 - b[3]; + out[4] = a[4] + t54m8 - b[4]; +} +static void fscalar(felem out, const felem in) { /* * 121665 */ + u128 a; + a = (u128)in[0] * 121665; out[0] = (uint64_t)a & FE_MASK; + a = (u128)in[1] * 121665 + (uint64_t)(a >> 51); out[1] = (uint64_t)a & FE_MASK; + a = (u128)in[2] * 121665 + (uint64_t)(a >> 51); out[2] = (uint64_t)a & FE_MASK; + a = (u128)in[3] * 121665 + (uint64_t)(a >> 51); out[3] = (uint64_t)a & FE_MASK; + a = (u128)in[4] * 121665 + (uint64_t)(a >> 51); out[4] = (uint64_t)a & FE_MASK; + out[0] += 19 * (uint64_t)(a >> 51); +} +static void fmul(felem out, const felem in2, const felem in) { + u128 t[5]; + uint64_t r0 = in[0], r1 = in[1], r2 = in[2], r3 = in[3], r4 = in[4]; + uint64_t s0 = in2[0], s1 = in2[1], s2 = in2[2], s3 = in2[3], s4 = in2[4], c; + t[0] = (u128)r0 * s0; + t[1] = (u128)r0 * s1 + (u128)r1 * s0; + t[2] = (u128)r0 * s2 + (u128)r2 * s0 + (u128)r1 * s1; + t[3] = (u128)r0 * s3 + (u128)r3 * s0 + (u128)r1 * s2 + (u128)r2 * s1; + t[4] = (u128)r0 * s4 + (u128)r4 * s0 + (u128)r3 * s1 + (u128)r1 * s3 + + (u128)r2 * s2; + r4 *= 19; r1 *= 19; r2 *= 19; r3 *= 19; + t[0] += (u128)r4 * s1 + (u128)r1 * s4 + (u128)r2 * s3 + (u128)r3 * s2; + t[1] += (u128)r4 * s2 + (u128)r2 * s4 + (u128)r3 * s3; + t[2] += (u128)r4 * s3 + (u128)r3 * s4; + t[3] += (u128)r4 * s4; + c = (uint64_t)(t[0] >> 51); r0 = (uint64_t)t[0] & FE_MASK; + t[1] += c; c = (uint64_t)(t[1] >> 51); r1 = (uint64_t)t[1] & FE_MASK; + t[2] += c; c = (uint64_t)(t[2] >> 51); r2 = (uint64_t)t[2] & FE_MASK; + t[3] += c; c = (uint64_t)(t[3] >> 51); r3 = (uint64_t)t[3] & FE_MASK; + t[4] += c; c = (uint64_t)(t[4] >> 51); r4 = (uint64_t)t[4] & FE_MASK; + r0 += c * 19; c = r0 >> 51; r0 &= FE_MASK; + r1 += c; c = r1 >> 51; r1 &= FE_MASK; r2 += c; + out[0] = r0; out[1] = r1; out[2] = r2; out[3] = r3; out[4] = r4; +} +static void fsquare(felem out, const felem in) { fmul(out, in, in); } + +static void fmontswap(felem a, felem b, uint64_t iswap) { + uint64_t m = (uint64_t)(-(int64_t)iswap); + for (int i = 0; i < 5; i++) { + uint64_t x = m & (a[i] ^ b[i]); + a[i] ^= x; b[i] ^= x; + } +} + +/* out = x^(2^255-21) = x^(p-2), the field inverse (donna's addition chain). */ +static void crecip(felem out, const felem z) { + felem a, t0, b, c; + int i; + fsquare(a, z); /* 2 */ + fsquare(t0, a); fsquare(t0, t0); fmul(b, t0, z); /* 9 */ + fmul(a, b, a); /* 11 */ + fsquare(t0, a); fmul(b, t0, b); /* 2^5 - 2^0 */ + fsquare(t0, b); for (i = 1; i < 5; i++) fsquare(t0, t0); fmul(b, t0, b); + fsquare(t0, b); for (i = 1; i < 10; i++) fsquare(t0, t0); fmul(c, t0, b); + fsquare(t0, c); for (i = 1; i < 20; i++) fsquare(t0, t0); fmul(t0, t0, c); + fsquare(t0, t0); for (i = 1; i < 10; i++) fsquare(t0, t0); fmul(b, t0, b); + fsquare(t0, b); for (i = 1; i < 50; i++) fsquare(t0, t0); fmul(c, t0, b); + fsquare(t0, c); for (i = 1; i < 100; i++) fsquare(t0, t0); fmul(t0, t0, c); + fsquare(t0, t0); for (i = 1; i < 50; i++) fsquare(t0, t0); fmul(t0, t0, b); + /* z^(2^250-1) -> 5 squarings -> z^(2^255-32), * z^11 -> z^(2^255-21) = z^(p-2) */ + for (i = 0; i < 5; i++) { fsquare(t0, t0); } + fmul(out, t0, a); +} + +static void cmult(felem outx, felem outz, const uint8_t *scalar, + const felem point) { + felem x1, x2, z2, x3, z3; + felem a, aa, b, bb, e, c, d, da, cb, t0, t1; + for (int i = 0; i < 5; i++) { x1[i] = point[i]; x3[i] = point[i]; } + for (int i = 0; i < 5; i++) { x2[i] = 0; z2[i] = 0; z3[i] = 0; } + x2[0] = 1; z3[0] = 1; + uint64_t swap = 0; + for (int t = 254; t >= 0; t--) { + uint64_t kt = (scalar[t >> 3] >> (t & 7)) & 1; + swap ^= kt; + fmontswap(x2, x3, swap); + fmontswap(z2, z3, swap); + swap = kt; + fsum(a, x2, z2); fdiff(b, x2, z2); + fsum(c, x3, z3); fdiff(d, x3, z3); + fmul(da, d, a); fmul(cb, c, b); + fsum(t0, da, cb); fdiff(t1, da, cb); + fsquare(x3, t0); fsquare(t1, t1); fmul(z3, x1, t1); + fsquare(aa, a); fsquare(bb, b); + fmul(x2, aa, bb); fdiff(e, aa, bb); + fscalar(t0, e); fsum(t0, aa, t0); fmul(z2, e, t0); + } + fmontswap(x2, x3, swap); + fmontswap(z2, z3, swap); + for (int i = 0; i < 5; i++) { outx[i] = x2[i]; outz[i] = z2[i]; } +} + +/* RFC 7748 X25519(scalar, u-coordinate) -> shared u-coordinate. */ +void wo_x25519(uint8_t out[32], const uint8_t scalar[32], + const uint8_t point[32]) { + uint8_t e[32]; + for (int i = 0; i < 32; i++) e[i] = scalar[i]; + e[0] &= 248; e[31] &= 127; e[31] |= 64; /* clamp */ + felem bp, x, z, zi; + fexpand(bp, point); + cmult(x, z, e, bp); + crecip(zi, z); + fmul(x, x, zi); + fcontract(out, x); +} + /* The VM half: Bytes in, fresh Bytes out. Wrong class id traps * WO_T_BOUNDS with the Bytes builtins' message shape. */ static const wo_str *arg_bytes(uint64_t r, const char **msg) { diff --git a/runtime/src/crypto.h b/runtime/src/crypto.h index b6ded4a..f2cabeb 100644 --- a/runtime/src/crypto.h +++ b/runtime/src/crypto.h @@ -48,6 +48,11 @@ int wo_hkdf_sha256_expand_label(const uint8_t secret[32], const char *label, size_t labellen, const uint8_t *ctx, size_t ctxlen, uint8_t *out, size_t outlen); +/* X25519 (rv2 9 phase C, RFC 7748) — internal C, consumed by the TLS ECDHE + * handshake. out = X25519(scalar, u-coordinate). */ +void wo_x25519(uint8_t out[32], const uint8_t scalar[32], + const uint8_t point[32]); + int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg); #endif diff --git a/runtime/test/test_crypto.c b/runtime/test/test_crypto.c index ecaa575..e53a5f8 100644 --- a/runtime/test/test_crypto.c +++ b/runtime/test/test_crypto.c @@ -52,6 +52,8 @@ static void t_poly1305(const uint8_t key[32], const char *msg, size_t mlen, T_CHECK(strcmp(got, want) == 0); } +static void t_x25519(const char *kh, const char *uh, const char *wanth); + static size_t unhex(const char *h, uint8_t *out) { size_t n = strlen(h) / 2; for (size_t i = 0; i < n; i++) { @@ -83,6 +85,15 @@ static void t_aesgcm(const char *kh, const char *ih, const char *ah, T_CHECK(wo_aes_gcm_open(key, klen, iv, aad, alen, out, plen, bad, back) == 1); } +static void t_x25519(const char *kh, const char *uh, const char *wanth) { + uint8_t k[32], u[32], out[32]; + char got[65]; + unhex(kh, k); unhex(uh, u); + wo_x25519(out, k, u); + hex(out, 32, got); + T_CHECK(strcmp(got, wanth) == 0); +} + int main(void) { /* RFC 3174 */ t_sha1("abc", 3, "a9993e364706816aba3e25717850c26c9cd0d89d"); @@ -288,5 +299,31 @@ int main(void) { "a5b1caa258481fdf573ac069f281e534e4a2379ec9e457e0c8494c227efb40e6") == 0); } + /* X25519 (rv2 9 phase C) — RFC 7748 §5.2 direct vectors */ + t_x25519("a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4", + "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c", + "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552"); + t_x25519("4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d", + "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493", + "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957"); + /* RFC 7748 §5.2 iterated test: k=u=9, iterate; check after 1 and 1000. */ + { + uint8_t k[32] = { 9 }, u[32] = { 9 }, r[32]; + char got[65]; + for (int it = 1; it <= 1000; it++) { + wo_x25519(r, k, u); + memcpy(u, k, 32); + memcpy(k, r, 32); + if (it == 1) { + hex(k, 32, got); + T_CHECK(strcmp(got, + "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079") == 0); + } + } + hex(k, 32, got); + T_CHECK(strcmp(got, + "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51") == 0); + } + return t_report("test_crypto"); }