Skip to content

File syn_ed25519.c

File List > crypto > syn_ed25519.c

Go to the documentation of this file

#include "syn_ed25519.h"

#include <string.h>

/* ── Curve25519 Field Arithmetic (GF(2^255 - 19), 16 × 16-bit limbs) ──────── */

typedef int64_t gf[16];

static const gf gf0 = {0};
static const gf gf1 = {1};

static const gf D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070,
                     0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203};

static const gf D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0,
                      0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406};

static const gf X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c,
                     0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169};

static const gf Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666,
                     0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666};

static const gf I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43,
                     0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83};

static void car25519(gf o)
{
    int i;
    for (i = 0; i < 16; i++) {
        o[i] += (1LL << 16);
        int64_t c = o[i] >> 16;
        o[(i + 1) * (i < 15)] += c - 1 + 37 * (c - 1) * (i == 15);
        o[i] -= c * 65536LL;
    }
}

static void sel25519(gf p, gf q, int b)
{
    int64_t t, c = ~(int64_t)(b - 1);
    for (int i = 0; i < 16; i++) {
        t = c & (p[i] ^ q[i]);
        p[i] ^= t;
        q[i] ^= t;
    }
}

static void pack25519(uint8_t o[32], const gf n)
{
    int i, j;
    int64_t b;
    gf m, t;
    for (i = 0; i < 16; i++) {
        t[i] = n[i];
    }
    car25519(t);
    car25519(t);
    car25519(t);
    for (j = 0; j < 2; j++) {
        m[0] = t[0] - 0xFFED;
        for (i = 1; i < 15; i++) {
            m[i] = t[i] - 0xFFFF - ((m[i - 1] >> 16) & 1);
            m[i - 1] &= 0xFFFF;
        }
        m[15] = t[15] - 0x7FFF - ((m[14] >> 16) & 1);
        b = (m[15] >> 16) & 1;
        m[14] &= 0xFFFF;
        sel25519(t, m, (int)(1 - b));
    }
    for (i = 0; i < 16; i++) {
        uint16_t v = (uint16_t)t[i];
        o[2 * i] = (uint8_t)(v & 0xFFU);
        o[2 * i + 1] = (uint8_t)((v >> 8) & 0xFFU);
    }
}

static int crypto_verify_32(const uint8_t *x, const uint8_t *y)
{
    uint32_t d = 0;
    for (int i = 0; i < 32; i++) {
        d |= (uint32_t)(x[i] ^ y[i]);
    }
    return (int)((1U & ((d - 1U) >> 8)) - 1U);
}

static int neq25519(const gf a, const gf b)
{
    uint8_t c[32], d[32];
    pack25519(c, a);
    pack25519(d, b);
    return crypto_verify_32(c, d);
}

static uint8_t par25519(const gf a)
{
    uint8_t d[32];
    pack25519(d, a);
    return (uint8_t)(d[0] & 1U);
}

static void unpack25519(gf o, const uint8_t n[32])
{
    for (int i = 0; i < 16; i++) {
        uint16_t v = (uint16_t)n[2 * i] | ((uint16_t)n[2 * i + 1] << 8);
        o[i] = (int64_t)v;
    }
    o[15] &= 0x7FFF;
}

static void A(gf o, const gf a, const gf b)
{
    for (int i = 0; i < 16; i++) {
        o[i] = a[i] + b[i];
    }
}

static void Z(gf o, const gf a, const gf b)
{
    for (int i = 0; i < 16; i++) {
        o[i] = a[i] - b[i];
    }
}

static void M(gf o, const gf a, const gf b)
{
    int64_t t[31];
    int i, j;
    for (i = 0; i < 31; i++) {
        t[i] = 0;
    }
    for (i = 0; i < 16; i++) {
        for (j = 0; j < 16; j++) {
            t[i + j] += a[i] * b[j];
        }
    }
    for (i = 0; i < 15; i++) {
        t[i] += 38 * t[i + 16];
    }
    for (i = 0; i < 16; i++) {
        o[i] = t[i];
    }
    car25519(o);
    car25519(o);
}

static void S(gf o, const gf a)
{
    M(o, a, a);
}

static void inv25519(gf o, const gf i_val)
{
    gf c;
    int a;
    for (a = 0; a < 16; a++) {
        c[a] = i_val[a];
    }
    for (a = 253; a >= 0; a--) {
        S(c, c);
        if (a != 2 && a != 4) {
            M(c, c, i_val);
        }
    }
    for (a = 0; a < 16; a++) {
        o[a] = c[a];
    }
}

static void pow2523(gf o, const gf i_val)
{
    gf c;
    int a;
    for (a = 0; a < 16; a++) {
        c[a] = i_val[a];
    }
    for (a = 250; a >= 0; a--) {
        S(c, c);
        if (a != 1) {
            M(c, c, i_val);
        }
    }
    for (a = 0; a < 16; a++) {
        o[a] = c[a];
    }
}

static void set25519(gf r, const gf a)
{
    for (int i = 0; i < 16; i++) {
        r[i] = a[i];
    }
}

/* ── Group Operations on Edwards25519 ─────────────────────────────────────── */

static void add(gf p[4], gf q[4])
{
    gf a, b, c, d_pt, t, e, f, g, h;

    Z(a, p[1], p[0]);
    Z(t, q[1], q[0]);
    M(a, a, t);
    A(b, p[0], p[1]);
    A(t, q[0], q[1]);
    M(b, b, t);
    M(c, p[3], q[3]);
    M(c, c, D2);
    M(d_pt, p[2], q[2]);
    A(d_pt, d_pt, d_pt);
    Z(e, b, a);
    Z(f, d_pt, c);
    A(g, d_pt, c);
    A(h, b, a);

    M(p[0], e, f);
    M(p[1], h, g);
    M(p[2], g, f);
    M(p[3], e, h);
}

static void cswap(gf p[4], gf q[4], uint8_t b)
{
    for (int i = 0; i < 4; i++) {
        sel25519(p[i], q[i], (int)b);
    }
}

static void pack(uint8_t *r, gf p[4])
{
    gf tx, ty, zi;
    inv25519(zi, p[2]);
    M(tx, p[0], zi);
    M(ty, p[1], zi);
    pack25519(r, ty);
    r[31] ^= (uint8_t)(par25519(tx) << 7);
}

static void scalarmult(gf p[4], gf q[4], const uint8_t *s)
{
    set25519(p[0], gf0);
    set25519(p[1], gf1);
    set25519(p[2], gf1);
    set25519(p[3], gf0);
    for (int i = 255; i >= 0; --i) {
        uint8_t b = (uint8_t)((s[i / 8] >> (i & 7)) & 1);
        cswap(p, q, b);
        add(q, p);
        add(p, p);
        cswap(p, q, b);
    }
}

static void scalarbase(gf p[4], const uint8_t *s)
{
    gf q[4];
    set25519(q[0], X);
    set25519(q[1], Y);
    set25519(q[2], gf1);
    M(q[3], X, Y);
    scalarmult(p, q, s);
}

static int unpackneg(gf r[4], const uint8_t p[32])
{
    gf t, chk, num, den, den2, den4, den6;
    set25519(r[2], gf1);
    unpack25519(r[1], p);
    S(num, r[1]);
    M(den, num, D);
    Z(num, num, r[2]);
    A(den, r[2], den);

    S(den2, den);
    S(den4, den2);
    M(den6, den4, den2);
    M(t, den6, num);
    M(t, t, den);

    pow2523(t, t);
    M(t, t, num);
    M(t, t, den);
    M(t, t, den);
    M(r[0], t, den);

    S(chk, r[0]);
    M(chk, chk, den);
    if (neq25519(chk, num) != 0) {
        M(r[0], r[0], I);
    }

    S(chk, r[0]);
    M(chk, chk, den);
    if (neq25519(chk, num) != 0) {
        return -1;
    }

    if (par25519(r[0]) == (uint8_t)(p[31] >> 7)) {
        Z(r[0], gf0, r[0]);
    }

    M(r[3], r[0], r[1]);
    return 0;
}

/* ── Scalar Arithmetic Modulo L (RFC 8032 Section 5.1) ───────────────────── */

static const int64_t ORDER_L[32] = {
    0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0x10};

static void modL(uint8_t *r, int64_t x[64])
{
    int64_t carry, i, j;
    for (i = 63; i >= 32; --i) {
        carry = 0;
        for (j = i - 32; j < i - 12; ++j) {
            x[j] += carry - 16 * x[i] * ORDER_L[j - (i - 32)];
            carry = (x[j] + 128) >> 8;
            x[j] -= carry * 256;
        }
        x[j] += carry;
        x[i] = 0;
    }
    carry = 0;
    for (j = 0; j < 32; j++) {
        x[j] += carry - (x[31] >> 4) * ORDER_L[j];
        carry = x[j] >> 8;
        x[j] &= 255;
    }
    for (j = 0; j < 32; j++) {
        x[j] -= carry * ORDER_L[j];
    }
    for (i = 0; i < 32; i++) {
        x[i + 1] += x[i] >> 8;
        r[i] = (uint8_t)(x[i] & 255);
    }
}

static void reduce(uint8_t *r)
{
    int64_t x[64];
    for (int i = 0; i < 64; i++) {
        x[i] = (int64_t)r[i];
    }
    for (int i = 0; i < 64; i++) {
        r[i] = 0;
    }
    modL(r, x);
}

/* ── Public API ──────────────────────────────────────────────────────────── */

bool syn_ed25519_publickey(const uint8_t secret_key[SYN_ED25519_SECRET_KEY_SIZE],
                           uint8_t public_key[SYN_ED25519_PUBLIC_KEY_SIZE])
{
    if (secret_key == NULL || public_key == NULL) {
        return false;
    }

    uint8_t d[64];
    gf p[4];

    SYN_SHA512_Ctx hash_ctx;
    syn_sha512_init(&hash_ctx);
    syn_sha512_update(&hash_ctx, secret_key, SYN_ED25519_SECRET_KEY_SIZE);
    syn_sha512_final(&hash_ctx, d);

    d[0] &= 248U;
    d[31] &= 127U;
    d[31] |= 64U;

    scalarbase(p, d);
    pack(public_key, p);

    volatile uint8_t *vp = (volatile uint8_t *)d;
    for (size_t i = 0; i < sizeof(d); i++) {
        vp[i] = 0U;
    }
    return true;
}

bool syn_ed25519_create_keypair(uint8_t public_key[SYN_ED25519_PUBLIC_KEY_SIZE],
                                uint8_t secret_key[SYN_ED25519_SECRET_KEY_SIZE],
                                const uint8_t seed[SYN_ED25519_SEED_SIZE])
{
    if (public_key == NULL || secret_key == NULL || seed == NULL) {
        return false;
    }

    (void)memcpy(secret_key, seed, SYN_ED25519_SECRET_KEY_SIZE);
    return syn_ed25519_publickey(secret_key, public_key);
}

bool syn_ed25519_sign(const uint8_t *msg, size_t msg_len,
                      const uint8_t secret_key[SYN_ED25519_SECRET_KEY_SIZE],
                      const uint8_t public_key[SYN_ED25519_PUBLIC_KEY_SIZE],
                      uint8_t sig[SYN_ED25519_SIGNATURE_SIZE])
{
    if ((msg == NULL && msg_len > 0U) || secret_key == NULL || sig == NULL) {
        return false;
    }

    uint8_t pk_buf[SYN_ED25519_PUBLIC_KEY_SIZE];
    const uint8_t *pk = public_key;
    if (pk == NULL) {
        (void)syn_ed25519_publickey(secret_key, pk_buf);
        pk = pk_buf;
    }

    uint8_t d[64], r[64];
    int64_t x[64];
    gf p[4];

    SYN_SHA512_Ctx hash_ctx;
    syn_sha512_init(&hash_ctx);
    syn_sha512_update(&hash_ctx, secret_key, SYN_ED25519_SECRET_KEY_SIZE);
    syn_sha512_final(&hash_ctx, d);

    d[0] &= 248U;
    d[31] &= 127U;
    d[31] |= 64U;

    /* Nonce r = SHA-512(d[32..63] || msg) */
    syn_sha512_init(&hash_ctx);
    syn_sha512_update(&hash_ctx, d + 32, 32U);
    if (msg != NULL && msg_len > 0U) {
        syn_sha512_update(&hash_ctx, msg, msg_len);
    }
    syn_sha512_final(&hash_ctx, r);
    reduce(r);

    /* R = r * B */
    scalarbase(p, r);
    pack(sig, p);

    /* Challenge h = SHA-512(R || pk || msg) */
    syn_sha512_init(&hash_ctx);
    syn_sha512_update(&hash_ctx, sig, 32U);
    syn_sha512_update(&hash_ctx, pk, 32U);
    if (msg != NULL && msg_len > 0U) {
        syn_sha512_update(&hash_ctx, msg, msg_len);
    }
    uint8_t h[64];
    syn_sha512_final(&hash_ctx, h);
    reduce(h);

    /* S = (r + h * d) mod L */
    for (int i = 0; i < 64; i++) {
        x[i] = 0;
    }
    for (int i = 0; i < 32; i++) {
        x[i] = (int64_t)r[i];
    }
    for (int i = 0; i < 32; i++) {
        for (int j = 0; j < 32; j++) {
            x[i + j] += (int64_t)h[i] * (int64_t)d[j];
        }
    }
    modL(sig + 32, x);

    volatile uint8_t *vp = (volatile uint8_t *)d;
    for (size_t i = 0; i < sizeof(d); i++) {
        vp[i] = 0U;
    }
    vp = (volatile uint8_t *)r;
    for (size_t i = 0; i < sizeof(r); i++) {
        vp[i] = 0U;
    }
    return true;
}

bool syn_ed25519_verify_hash(const uint8_t sig[SYN_ED25519_SIGNATURE_SIZE], const uint8_t h[64],
                             const uint8_t public_key[SYN_ED25519_PUBLIC_KEY_SIZE])
{
    if (sig == NULL || h == NULL || public_key == NULL) {
        return false;
    }

    if ((sig[63] & 224U) != 0U) {
        return false;
    }

    gf p[4], q[4];
    if (unpackneg(q, public_key) != 0) {
        return false;
    }

    uint8_t h_reduced[64];
    for (size_t i = 0; i < 64U; i++) {
        h_reduced[i] = h[i];
    }
    reduce(h_reduced);

    scalarmult(p, q, h_reduced);
    scalarbase(q, sig + 32);
    add(p, q);

    uint8_t t[32];
    pack(t, p);

    return (crypto_verify_32(sig, t) == 0);
}

bool syn_ed25519_verify(const uint8_t sig[SYN_ED25519_SIGNATURE_SIZE], const uint8_t *msg,
                        size_t msg_len, const uint8_t public_key[SYN_ED25519_PUBLIC_KEY_SIZE])
{
    if (sig == NULL || (msg == NULL && msg_len > 0U) || public_key == NULL) {
        return false;
    }

    /* Challenge h = SHA-512(R || pk || msg) */
    SYN_SHA512_Ctx hash_ctx;
    syn_sha512_init(&hash_ctx);
    syn_sha512_update(&hash_ctx, sig, 32U);
    syn_sha512_update(&hash_ctx, public_key, 32U);
    if (msg != NULL && msg_len > 0U) {
        syn_sha512_update(&hash_ctx, msg, msg_len);
    }
    uint8_t h[64];
    syn_sha512_final(&hash_ctx, h);

    return syn_ed25519_verify_hash(sig, h, public_key);
}