// pub-verify -- verify a chain of cm/pub keys
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
#pragma clang diagnostic ignored "-Wreserved-id-macro"
#include <gcl.h>
#pragma clang diagnostic pop

#include "verifier-gost3410.h"

#include "curves.c.in"

static unsigned char *
align16(unsigned char *p)
{
    unsigned int n = (uint64_t)p % 16;
    if (n == 0) {
        return p;
    }
    return p + (ptrdiff_t)(16 - n);
}

bool
gost3410SignatureVerifier(
    char **failReason,
    const unsigned char *ai,
    const size_t aiLen,
    const unsigned char *sig,
    const size_t sigLen,
    const unsigned char *pub,
    const size_t pubLen,
    const unsigned char *data,
    const size_t dataLen)
{
    const char *ai256A = "gost3410-256A";
    const char *ai512C = "gost3410-512C";
    size_t pointLen = 0;
    bool is256 = false;
    if ((aiLen == strlen(ai256A)) && (memcmp(ai, ai256A, aiLen) == 0)) {
        is256 = true;
        pointLen = 32;
    } else if ((aiLen == strlen(ai512C)) && (memcmp(ai, ai512C, aiLen) == 0)) {
        is256 = false;
        pointLen = 64;
    } else {
        (*failReason) = "unknown ai";
        return false;
    }
    if (pubLen != pointLen * 2) {
        (*failReason) = "invalid pubkey len";
        return false;
    }
    if (sigLen != pointLen * 2) {
        (*failReason) = "invalid signature len";
        return false;
    }
    unsigned char hsh[64] = {0};
    int gcl3rc = GCL_RC_FAILED;
    {
        unsigned char ctx[GCL_GOSTR34112012_CTX_SIZE] = {0};
        GclGostR34112012Ctx *h = NULL;
        if (is256) {
            h = gcl_gostr34112012_ctx256_init_mem(
                (GclGostR34112012Ctx *)align16(ctx), &gcl3rc);
        } else {
            h = gcl_gostr34112012_ctx512_init_mem(
                (GclGostR34112012Ctx *)align16(ctx), &gcl3rc);
        }
        if (gcl3rc != GCL_RC_OK) {
            (*failReason) = "gcl_gostr34112012_ctxX_init_mem failed";
            return false;
        }
        gcl3rc = gcl_gostr34112012_hash_update(h, data, dataLen);
        if (gcl3rc != GCL_RC_OK) {
            (*failReason) = "gcl_gostr34112012_hash_update failed";
            return false;
        }
        if (is256) {
            gcl3rc = gcl_gostr34112012_hash256_finalize(h, hsh);
        } else {
            gcl3rc = gcl_gostr34112012_hash512_finalize(h, hsh);
        }
        gcl_gostr34112012_ctx_clean(h);
        if (gcl3rc != GCL_RC_OK) {
            (*failReason) = "gcl_gostr34112012_hashX_finalize failed";
            return false;
        }
    }
    {
        unsigned char ctx[GCL_GOSTR3410_CTX_SIZE] = {0};
        if (is256) {
            gcl_edwards_ctx_init_mem_varsize(
                (GclGostR3410Ctx *)ctx,
                gost3410Curve256Ap,
                gost3410Curve256Aq,
                gost3410Curve256Ae,
                gost3410Curve256Ad,
                gost3410Curve256Au,
                gost3410Curve256Av,
                256,
                &gcl3rc);
        } else {
            gcl_edwards_ctx_init_mem_varsize(
                (GclGostR3410Ctx *)ctx,
                gost3410Curve512Cp,
                gost3410Curve512Cq,
                gost3410Curve512Ce,
                gost3410Curve512Cd,
                gost3410Curve512Cu,
                gost3410Curve512Cv,
                512,
                &gcl3rc);
        }
        if (gcl3rc != GCL_RC_OK) {
            (*failReason) = "gcl_edwards_ctx_init_mem_varsize failed";
            return false;
        }
        gcl3rc = gcl_gostr3410_ctx_set_public_key_varsize(
            (GclGostR3410Ctx *)ctx, pub, pub + pointLen, pointLen * 8);
        if (gcl3rc != GCL_RC_OK) {
            gcl_gostr3410_ctx_clean((GclGostR3410Ctx *)ctx);
            (*failReason) = "gcl_gostr3410_ctx_set_public_key_varsize failed";
            return false;
        }
        gcl3rc = gcl_gostr3410_verify_varsize(
            (GclGostR3410Ctx *)ctx, hsh, pointLen, sig, pointLen * 2);
        gcl_gostr3410_ctx_clean((GclGostR3410Ctx *)ctx);
        switch (gcl3rc) {
        case GCL_RC_OK:
            return true;
        case GCL_RC_ECINVALID:
            (*failReason) = "invalid signature";
            break;
        default:
            (*failReason) = "gcl_gostr3410_ctx_set_public_key_varsize failed";
        }
    }
    return false;
}
