Commit 211888a2 authored by jan.koester's avatar jan.koester
Browse files

test

parent 10415f7d
Loading
Loading
Loading
Loading
+301 −0
Original line number Diff line number Diff line
#include "crypto/ecc_p256.h"
#include "crypto/ecc_u256.h"
#include <vector>
#include <cstdint>

namespace netplus {

// ------------------------------------------------------------
// Curve constants (NIST P-256 / secp256r1)
// ------------------------------------------------------------

// b coefficient (NORMAL domain literal, then converted once into Montgomery)

static const u256 P256_B_STD = {
    0x27D2604B, 0x3BCE3C3E, 0xCC53B0F6, 0x651D06B0,
    0x769886BC, 0xB3EBBD55, 0xAA3A93E7, 0x5AC635D8
};

const u256 P256_B = to_mont(P256_B_STD);

// ------------------------------------------------------------
// Base point G (Generator) in Montgomery domain
// ------------------------------------------------------------

// Base point in standard (normal) representation
static const P256Point P256_G_STD = []{
    P256Point g;
    g.inf = false;

    // Gx (std domain)
    g.x = {0xd898c296, 0xf4a13945, 0x2deb33a0, 0x77037d81,
           0x63a440f2, 0xf8bce6e5, 0xe12c4247, 0x6b17d1f2};

    // Gy (std domain)
    g.y = {0x37bf51f5, 0xcbb64068, 0x6b315ece, 0x2bce3357,
           0x7c0f9e16, 0x8ee7eb4a, 0xfe1a7f9b, 0x4fe342e2};

    return g;
}();

static const P256Point P256_G_MONT = []{
    P256Point g = P256_G_STD;
    g.x = to_mont(g.x);
    g.y = to_mont(g.y);
    g.inf = false;
    return g;
}();

const P256Point& P256_G() { return P256_G_MONT; }

// ------------------------------------------------------------
// Point Transformations
// ------------------------------------------------------------

/**
 * Converts Affine (Montgomery) to Jacobian (Montgomery).
 * Z starts as 1 in Montgomery domain: Z = to_mont(1).
 */
P256PointJ toJac(const P256Point& a) {
    P256PointJ r;
    r.inf = a.inf;
    r.X = a.x;
    r.Y = a.y;

    // Z = 1 (Montgomery)
    u256 one = {{1,0,0,0,0,0,0,0}};
    r.Z = to_mont(one);

    return r;
}

/**
 * Converts Jacobian (Montgomery) to Affine (Montgomery).
 * x = X / Z^2, y = Y / Z^3  (all Montgomery domain)
 */
P256Point toAff(const P256PointJ& a) {
    P256Point r;
    if (a.inf || u256_is_zero(a.Z)) {
        r.inf = true;
        return r;
    }

    // zi = 1/Z (Montgomery)
    u256 zi  = fp_inv(a.Z);
    u256 zi2 = fp_sqr(zi);
    u256 zi3 = fp_mul(zi2, zi);

    r.x = fp_mul(a.X, zi2);
    r.y = fp_mul(a.Y, zi3);
    r.inf = false;
    return r;
}

// ------------------------------------------------------------
// Point Arithmetic
// ------------------------------------------------------------

/**
 * Jacobian point doubling: R = 2P  (all Montgomery domain)
 * Optimized for a=-3.
 */
P256PointJ point_double(const P256PointJ& a) {
    if (a.inf || u256_is_zero(a.Z)) return a;

    u256 t1 = fp_sqr(a.Z);              // Z^2
    u256 t2 = fp_sub(a.X, t1);          // X - Z^2
    u256 t3 = fp_add(a.X, t1);          // X + Z^2
    t2 = fp_mul(t2, t3);                // (X-Z^2)(X+Z^2)
    u256 t4 = fp_add(t2, t2);           // 2*t2
    u256 M  = fp_add(t4, t2);           // 3*(X^2 - Z^4)

    u256 Y2 = fp_sqr(a.Y);              // Y^2
    u256 S  = fp_mul(a.X, Y2);          // X*Y^2
    S = fp_add(S, S);
    S = fp_add(S, S);                   // S = 4*X*Y^2

    u256 T  = fp_sqr(M);                // M^2
    u256 S2 = fp_add(S, S);             // 2S
    u256 X3 = fp_sub(T, S2);

    u256 Y4 = fp_sqr(Y2);
    u256 Y8 = fp_add(Y4, Y4);
    Y8 = fp_add(Y8, Y8);
    Y8 = fp_add(Y8, Y8);                // 8*Y^4

    u256 S_minus_X3 = fp_sub(S, X3);
    u256 Y3 = fp_sub(fp_mul(M, S_minus_X3), Y8);

    u256 YZ = fp_mul(a.Y, a.Z);
    u256 Z3 = fp_add(YZ, YZ);

    P256PointJ r;
    r.inf = false;
    r.X = X3;
    r.Y = Y3;
    r.Z = Z3;
    return r;
}

/**
 * Full Jacobian point addition: R = a + b (Montgomery)
 */
P256PointJ point_add_jac(const P256PointJ& a, const P256PointJ& b) {
    if (a.inf) return b;
    if (b.inf) return a;

    u256 z1z1 = fp_sqr(a.Z);
    u256 z2z2 = fp_sqr(b.Z);

    u256 u1 = fp_mul(a.X, z2z2);
    u256 u2 = fp_mul(b.X, z1z1);

    u256 s1 = fp_mul(fp_mul(a.Y, b.Z), z2z2);
    u256 s2 = fp_mul(fp_mul(b.Y, a.Z), z1z1);

    if (u256_cmp(u1, u2) == 0) {
        if (u256_cmp(s1, s2) == 0) return point_double(a);
        P256PointJ r; r.inf = true; return r;
    }

    u256 h = fp_sub(u2, u1);
    u256 r_val = fp_sub(s2, s1);
    u256 h2 = fp_sqr(h);
    u256 h3 = fp_mul(h2, h);
    u256 v  = fp_mul(u1, h2);

    u256 x3 = fp_sub(fp_sub(fp_sqr(r_val), h3), fp_add(v, v));
    u256 y3 = fp_sub(fp_mul(r_val, fp_sub(v, x3)), fp_mul(s1, h3));
    u256 z3 = fp_mul(fp_mul(a.Z, b.Z), h);

    P256PointJ res;
    res.inf = false;
    res.X = x3;
    res.Y = y3;
    res.Z = z3;
    return res;
}

// ------------------------------------------------------------
// Scalar Multiplication (Montgomery Ladder)
// ------------------------------------------------------------

static inline void cswap_jac(P256PointJ& a, P256PointJ& b, uint32_t swap) {
    swap = 0 - swap; // 0x00000000 or 0xFFFFFFFF

    for (int i = 0; i < 8; i++) {
        uint32_t t;

        t = swap & (a.X.w[i] ^ b.X.w[i]); a.X.w[i] ^= t; b.X.w[i] ^= t;
        t = swap & (a.Y.w[i] ^ b.Y.w[i]); a.Y.w[i] ^= t; b.Y.w[i] ^= t;
        t = swap & (a.Z.w[i] ^ b.Z.w[i]); a.Z.w[i] ^= t; b.Z.w[i] ^= t;
    }

    uint32_t tinf = swap & (uint32_t)(a.inf ^ b.inf);
    a.inf ^= tinf;
    b.inf ^= tinf;
}

P256Point scalar_mul(const uint8_t priv[32], const P256Point& P) {
    P256PointJ R0; R0.inf = true;

    // For infinity point, set dummy values in Montgomery (so ops are defined)
    u256 zero; u256_zero(zero);
    R0.X = to_mont(zero);
    R0.Y = to_mont(zero);
    R0.Z = to_mont(zero);

    P256PointJ R1 = toJac(P);

    uint32_t prev = 0;
    for (int i = 0; i < 256; i++) {
        uint32_t b = get_bit_be(priv, i);
        uint32_t swap = b ^ prev;
        prev = b;

        cswap_jac(R0, R1, swap);

        R1 = point_add_jac(R0, R1);
        R0 = point_double(R0);
    }
    cswap_jac(R0, R1, prev);
    return toAff(R0);
}

P256Point scalar_mul_G(const uint8_t priv[32]) {
    return scalar_mul(priv, P256_G());
}

// ------------------------------------------------------------
// TLS / NIST Interoperability
// ------------------------------------------------------------

/**
 * Decodes uncompressed TLS point (04||X||Y).
 * Internally stores coordinates in Montgomery domain.
 * Also validates curve equation in Montgomery domain.
 */
bool decode_tls_point(P256Point& out, const uint8_t* p, size_t n) {
    if (!p || n < 65 || p[0] != 0x04) return false;

    // Read coordinates from network Big Endian -> normal u256
    u256 x_std = u256_from_be(p + 1);
    u256 y_std = u256_from_be(p + 33);

    // Convert to Montgomery internal representation
    out.x = to_mont(x_std);
    out.y = to_mont(y_std);
    out.inf = false;

    // Validate y^2 == x^3 - 3x + b  (all Montgomery)
    u256 y2 = fp_sqr(out.y);
    u256 x2 = fp_sqr(out.x);
    u256 x3 = fp_mul(x2, out.x);

    u256 three_x = fp_add(out.x, out.x);
    three_x = fp_add(three_x, out.x);

    u256 rhs = fp_sub(x3, three_x);
    rhs = fp_add(rhs, P256_B);

    if (u256_cmp(y2, rhs) != 0) {
        out.inf = true;
        return false;
    }
    return true;
}

/**
 * Encodes affine point (Montgomery) to TLS (std big endian).
 */
std::vector<uint8_t> encode_tls_point(const P256Point& P) {
    if (P.inf) return {};

    std::vector<uint8_t> res(65);
    res[0] = 0x04;

    // Convert from Montgomery -> standard for output
    u256 x_std = from_mont(P.x);
    u256 y_std = from_mont(P.y);

    u256_to_be(res.data() + 1,  x_std);
    u256_to_be(res.data() + 33, y_std);

    return res;
}

/**
 * ECDH: returns Big-Endian X-coordinate of shared point.
 */
bool ecdh_shared_secret(uint8_t out32[32], const uint8_t priv[32], const P256Point& peer) {
    if (!out32 || peer.inf) return false;

    P256Point shared_point = scalar_mul(priv, peer);
    if (shared_point.inf) return false;

    u256 x_std = from_mont(shared_point.x);
    u256_to_be(out32, x_std);
    return true;
}

} // namespace netplus

src/crypto/ecc_p256.h

0 → 100644
+110 −0
Original line number Diff line number Diff line
#pragma once
#include <vector>
#include <cstdint>
#include <cstring>
#include "ecc_u256.h"   // Must provide u256 type and fp_* arithmetic

namespace netplus {

/**
 * @brief Affine point on the NIST P-256 curve.
 * Equation: y^2 = x^3 - 3x + b (mod p)
 * Internal layout: x.w[0] is the Least Significant Word (Little-Endian math).
 */
struct P256Point {
    u256 x;
    u256 y;
    bool inf = true; // Flag for Point at Infinity (neutral element)
};

/**
 * @brief Jacobian point representation (X:Y:Z).
 * Conversion to affine: x = X/Z^2, y = Y/Z^3.
 * Used for efficient point arithmetic to avoid frequent modular inversions.
 */
struct P256PointJ {
    u256 X;
    u256 Y;
    u256 Z;
    bool inf = true;
};

// --- NIST P-256 Curve Constants ---

/**
 * @brief Returns a reference to the NIST P-256 Base Point G.
 */
const P256Point& P256_G();

/**
 * @brief The 'b' coefficient of the NIST P-256 curve equation.
 */
extern const u256 P256_B;

// --- Point Operations ---

/** * @brief Converts affine coordinates to Jacobian coordinates.
 * Sets Z = 1.
 */
P256PointJ toJac(const P256Point& a);

/** * @brief Converts Jacobian coordinates back to Affine.
 * Requires one modular inversion.
 */
P256Point  toAff(const P256PointJ& a);

/** * @brief Point doubling: R = 2 * P.
 * Optimized for the NIST case where a = -3.
 */
P256PointJ point_double(const P256PointJ& a);

/** * @brief Full Jacobian point addition: R = P1 + P2.
 */
P256PointJ point_add_jac(const P256PointJ& a, const P256PointJ& b);

/** * @brief Mixed addition: R = P1 (Jacobian) + P2 (Affine).
 * Faster than full Jacobian addition when one point is already affine.
 */
P256PointJ point_add_mixed(const P256PointJ& a, const P256Point& b);

// --- Scalar Multiplication ---

/**
 * @brief Computes R = k * P using a Montgomery Ladder.
 * @param priv 32-byte scalar (Big-Endian, as used in TLS/NIST).
 * @param P Affine point on the curve.
 * @return The resulting affine point.
 */
P256Point scalar_mul(const uint8_t priv[32], const P256Point& P);

/** * @brief Computes R = k * G (Base point multiplication).
 */
P256Point scalar_mul_G(const uint8_t priv[32]);

// --- TLS / Interoperability ---

/**
 * @brief Encodes a point into the uncompressed TLS format (0x04 || X || Y).
 * @return A 65-byte vector in Big-Endian format.
 */
std::vector<uint8_t> encode_tls_point(const P256Point& P);

/**
 * @brief Decodes an uncompressed point and validates that it lies on the curve.
 * Mandatory check for NIST compliance to prevent Invalid Curve attacks.
 * @param p Pointer to input data (starting with 0x04).
 * @param n Length of input (must be 65).
 */
bool decode_tls_point(P256Point& out, const uint8_t* p, size_t n);

/**
 * @brief ECDH Shared Secret calculation.
 * Extracts the X-coordinate of the resulting point (k * Peer_Public).
 * @param out32 Target buffer for the secret (32 bytes, Big-Endian).
 * @param priv Local private key.
 * @param peer Remote peer's public key.
 * @return true on success, false if the resulting point is at infinity.
 */
bool ecdh_shared_secret(uint8_t out32[32], const uint8_t priv[32], const P256Point& peer);

} // namespace netplus
+429 −0
Original line number Diff line number Diff line
#include "crypto/ecc_u256.h"
#include <cstring>
#include <cstdio>

namespace netplus {

// ============================================================================
// P-256 prime:
// p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
// internal u256 is LE words: w[0] = least significant 32 bits
// ============================================================================
const u256 P256_P = {{
    0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0x00000000u,
    0x00000000u, 0x00000000u, 0x00000001u, 0xFFFFFFFFu
}};

static const u256 P256_R = {{
    0x00000001u,
    0x00000000u,
    0x00000000u,
    0xFFFFFFFFu,
    0xFFFFFFFFu,
    0xFFFFFFFFu,
    0xFFFFFFFEu,
    0x00000000u
}};

// R^2 mod p  (critical!)
//
// Correct:
// R^2 mod p = 0x00000004fffffffdfffffffffffffffefffffffbffffffff0000000000000003
//
const u256 P256_R2 = {{
    0x00000003,
    0x00000000,
    0xFFFFFFFF,
    0xFFFFFFFB,
    0xFFFFFFFE,
    0xFFFFFFFF,
    0xFFFFFFFD,
    0x00000004
}};

// n0 = -p^{-1} mod 2^32. Since p0 = 0xffffffff, inverse is 1, so n0=1.
static constexpr uint32_t P256_N0 = 1u;

// ----------------------------------------------------------------------------
// basic helpers
// ----------------------------------------------------------------------------
void u256_zero(u256& a) { std::memset(a.w, 0, sizeof(a.w)); }

bool u256_is_zero(const u256& a) {
    for (int i = 0; i < 8; i++) if (a.w[i]) return false;
    return true;
}

int u256_cmp(const u256& a, const u256& b) {
    for (int i = 7; i >= 0; --i) {
        if (a.w[i] < b.w[i]) return -1;
        if (a.w[i] > b.w[i]) return 1;
    }
    return 0;
}

u256 u256_add_raw(const u256& a, const u256& b, uint32_t& carry) {
    u256 r;
    uint64_t c = 0;
    for (int i = 0; i < 8; i++) {
        uint64_t s = (uint64_t)a.w[i] + b.w[i] + c;
        r.w[i] = (uint32_t)s;
        c = s >> 32;
    }
    carry = (uint32_t)c;
    return r;
}

u256 u256_sub_raw(const u256& a, const u256& b, uint32_t& borrow) {
    u256 r;
    uint64_t br = 0;
    for (int i = 0; i < 8; i++) {
        uint64_t ai = (uint64_t)a.w[i];
        uint64_t bi = (uint64_t)b.w[i];
        uint64_t d  = ai - bi - br;
        r.w[i] = (uint32_t)d;
        br = (d >> 63) & 1;
    }
    borrow = (uint32_t)br;
    return r;
}

// ----------------------------------------------------------------------------
// BE/LE conversion
// ----------------------------------------------------------------------------
u256 u256_from_be(const uint8_t be32[32]) {
    u256 r;
    for (int i = 0; i < 8; i++) {
        int o = 28 - 4*i;
        r.w[i] =
            ((uint32_t)be32[o+0] << 24) |
            ((uint32_t)be32[o+1] << 16) |
            ((uint32_t)be32[o+2] << 8)  |
            ((uint32_t)be32[o+3] << 0);
    }
    return r;
}

void u256_to_be(uint8_t out32[32], const u256& a) {
    for (int i = 0; i < 8; i++) {
        uint32_t w = a.w[i];
        int o = 28 - 4*i;
        out32[o+0] = (uint8_t)(w >> 24);
        out32[o+1] = (uint8_t)(w >> 16);
        out32[o+2] = (uint8_t)(w >> 8);
        out32[o+3] = (uint8_t)(w >> 0);
    }
}

// ============================================================================
// 256x256 -> 512 multiply (schoolbook, correct carry), NO __int128
// ============================================================================
u512 u256_mul_raw(const u256& a, const u256& b) {
    u512 out;
    for (int i=0; i<16; i++) out.w[i] = 0;

    for (int i=0; i<8; i++){
        uint64_t c = 0;
        // Innere Schleife: Multiplikation und Addition
        for (int j=0; j<8; j++){
            uint64_t uv = (uint64_t)out.w[i+j] + (uint64_t)a.w[i]*b.w[j] + c;
            out.w[i+j] = (uint32_t)uv;
            c = uv >> 32;
        }

        // Übertrag auf die nächsten Wörter propagieren
        // Wir nutzen hier uint64_t, um Überläufe sicher zu handhaben
        int k = i + 8;
        while (c > 0 && k < 16) {
            uint64_t uv = (uint64_t)out.w[k] + c;
            out.w[k] = (uint32_t)uv;
            c = uv >> 32; // Wird nur > 0 sein, wenn out.w[k] überlief
            k++;
        }
    }
    return out;
}

// input: 64 bytes Big Endian (Network order)
// output: u512 with w[0] = least significant 32-bit word
static inline u512 u512_from_be64(const uint8_t be64[64]) {
    u512 r;
    for (int i = 0; i < 16; i++) {
        // be64[0..3] = MSW, be64[60..63] = LSW
        int o = 4 * i; // read MSW -> LSW in increasing offset
        uint32_t w =
            ((uint32_t)be64[o + 0] << 24) |
            ((uint32_t)be64[o + 1] << 16) |
            ((uint32_t)be64[o + 2] <<  8) |
            ((uint32_t)be64[o + 3] <<  0);

        // store into LE word array:
        r.w[15 - i] = w;
    }
    return r;
}


// ============================================================================
// Montgomery REDC for P-256 (8 limbs, 32-bit, no __int128)
// This version is stable and matches classic HAC REDC.
// ============================================================================
u256 mont_mul(const u256& a, const u256& b) {
    uint32_t t[16] = {0};
    uint32_t extra = 0; // overflow beyond t[15]

    // ---------------------------------------------------------
    // Step 1: t = a*b  (full 512-bit, propagate carry fully)
    // ---------------------------------------------------------
    for (int i=0; i<8; i++) {
        uint64_t carry = 0;
        for (int j=0; j<8; j++) {
            uint64_t uv = (uint64_t)t[i+j]
                        + (uint64_t)a.w[i] * b.w[j]
                        + carry;
            t[i+j] = (uint32_t)uv;
            carry  = uv >> 32;
        }

        int k = i + 8;
        while (carry) {
            if (k < 16) {
                uint64_t uv = (uint64_t)t[k] + carry;
                t[k] = (uint32_t)uv;
                carry = uv >> 32;
                k++;
            } else {
                extra += (uint32_t)carry; // should be only 1
                break;
            }
        }
    }

    // ---------------------------------------------------------
    // Step 2: Montgomery REDC
    // ---------------------------------------------------------
    for (int i=0; i<8; i++) {
        uint32_t m = t[i] * P256_N0; // N0=1 for P-256

        uint64_t carry = 0;
        for (int j=0; j<8; j++) {
            uint64_t uv = (uint64_t)t[i+j]
                        + (uint64_t)m * P256_P.w[j]
                        + carry;
            t[i+j] = (uint32_t)uv;
            carry  = uv >> 32;
        }

        int k = i + 8;
        while (carry) {
            if (k < 16) {
                uint64_t uv = (uint64_t)t[k] + carry;
                t[k] = (uint32_t)uv;
                carry = uv >> 32;
                k++;
            } else {
                extra += (uint32_t)carry;
                break;
            }
        }
    }

    // ---------------------------------------------------------
    // Step 3: r = t[8..15], normalize
    // ---------------------------------------------------------
    u256 r;
    for (int i=0; i<8; i++) r.w[i] = t[i+8];

    if (extra || u256_cmp(r, P256_P) >= 0) {
        uint32_t br=0;
        r = u256_sub_raw(r, P256_P, br);
    }
    if (u256_cmp(r, P256_P) >= 0) {
        uint32_t br=0;
        r = u256_sub_raw(r, P256_P, br);
    }

    return r;
}


// ----------------------------------------------------------------------------
// to/from Montgomery
// ----------------------------------------------------------------------------
u256 to_mont(const u256& a) {
    return mont_mul(a, P256_R2);
}

u256 from_mont(const u256& a) {
    u256 one; u256_zero(one); one.w[0] = 1;
    return mont_mul(a, one);
}

// ----------------------------------------------------------------------------
// field ops (Montgomery domain)
// ----------------------------------------------------------------------------
u256 fp_add(const u256& a, const u256& b) {
    uint32_t carry = 0;
    u256 r = u256_add_raw(a, b, carry);
    if (carry || u256_cmp(r, P256_P) >= 0) {
        uint32_t br = 0;
        r = u256_sub_raw(r, P256_P, br);
    }
    return r;
}

u256 fp_sub(const u256& a, const u256& b) {
    uint32_t br = 0;
    u256 r = u256_sub_raw(a, b, br);
    if (br) {
        uint32_t c = 0;
        r = u256_add_raw(r, P256_P, c);
    }
    return r;
}

u256 fp_mul(const u256& a, const u256& b) {
    return mont_mul(a, b);
}

u256 fp_sqr(const u256& a) {
    return mont_mul(a, a);
}

// ----------------------------------------------------------------------------
// bit access
// ----------------------------------------------------------------------------
uint32_t get_bit_be(const uint8_t priv[32], int bit_index) {
    int byte = bit_index / 8;
    int bit  = 7 - (bit_index % 8);
    return (priv[byte] >> bit) & 1;
}

u512 u512_from_be(const uint8_t in[64]) {
    u512 r;
    for (int i=0;i<16;i++){
        int o = 60 - 4*i;
        r.w[i] =
            ((uint32_t)in[o+0] << 24) |
            ((uint32_t)in[o+1] << 16) |
            ((uint32_t)in[o+2] <<  8) |
            ((uint32_t)in[o+3] <<  0);
    }
    return r; // r.w[0] is LSW
}

u256 mod_p256(const u512& in)
{
    const uint32_t* a = in.w; // little-endian 32-bit limbs

    int64_t t[16] = {0};

    // t = full 512-bit input (L + H*2^256)
    for(int i=0;i<16;i++) t[i] = (int64_t)a[i];

    auto add64 = [&](int idx, int64_t v){
        if ((unsigned)idx < 16) t[idx] += v;
    };
    auto sub64 = [&](int idx, int64_t v){
        if ((unsigned)idx < 16) t[idx] -= v;
    };

    // signed normalize across all 16 limbs
    auto normalize16 = [&](){
        int64_t carry = 0;
        for(int i=0;i<16;i++){
            t[i] += carry;
            carry = t[i] >> 32;
            t[i] &= 0xffffffffLL;
        }
        return carry; // should be small
    };

    // fold one high-limb word k at shift sh (word-shift)
    // k * 2^(256 + 32*sh)  with 2^256 ≡ 1 + 2^224 - 2^192 - 2^96
    auto fold_k_at_shift = [&](int64_t k, int sh){
        add64(sh + 0, k);  // +k
        add64(sh + 7, k);  // +k<<224
        sub64(sh + 6, k);  // -k<<192
        sub64(sh + 3, k);  // -k<<96
    };

    auto fold_hi = [&](int i){
        int64_t k = t[i];
        if(k == 0) return;
        t[i] = 0;
        int sh = i - 8;
        fold_k_at_shift(k, sh);
    };

    // First fold pass
    for(int i=15;i>=8;i--) fold_hi(i);
    int64_t top = normalize16();

    // Second fold pass (needed because fold can create new highs)
    for(int i=15;i>=8;i--) fold_hi(i);
    top += normalize16();

    // Fold remaining carry (treat as limb i=16 => sh=8)
    // top*2^512 = top * (2^256)^2 → folding top at sh=8 is enough once, then re-fold highs.
    while(top != 0){
        int64_t k = top;
        top = 0;
        fold_k_at_shift(k, 8);
        top += normalize16();
        for(int i=15;i>=8;i--) fold_hi(i);
        top += normalize16();
    }

    // extract low 256 bits
    u256 r;
    for(int i=0;i<8;i++) r.w[i] = (uint32_t)t[i];

    // constant-time subtract p twice (safe)
    auto ct_sub_p = [&](){
        uint32_t br=0;
        u256 diff = u256_sub_raw(r, P256_P, br);
        uint32_t mask = (uint32_t)0 - (uint32_t)(br ^ 1);
        for(int i=0;i<8;i++){
            r.w[i] = (r.w[i] & ~mask) | (diff.w[i] & mask);
        }
    };

    ct_sub_p();
    ct_sub_p();

    return r;
}

u256 mod_p256(const uint8_t be64[64]) {
    u512 x = u512_from_be64(be64);
    return mod_p256(x);
}

// ----------------------------------------------------------------------------
// exponentiation/inversion
// ----------------------------------------------------------------------------
static u256 fp_pow(const u256& a_mont, const u256& e_normal) {
    u256 one; u256_zero(one); one.w[0] = 1;
    u256 r = to_mont(one);

    for (int i = 7; i >= 0; i--) {
        for (int bit = 31; bit >= 0; bit--) {
            r = fp_sqr(r);
            if ((e_normal.w[i] >> bit) & 1) {
                r = fp_mul(r, a_mont);
            }
        }
    }
    return r;
}

u256 fp_inv(const u256& a_mont) {
    u256 exp = P256_P;
    u256 two; u256_zero(two); two.w[0] = 2;
    uint32_t br = 0;
    exp = u256_sub_raw(exp, two, br);
    return fp_pow(a_mont, exp);
}

} // namespace netplus

src/crypto/ecc_u256.h

0 → 100644
+50 −0
Original line number Diff line number Diff line
#pragma once
#include <cstdint>
#include <cstddef>

namespace netplus {

struct u256 { uint32_t w[8]; };
struct u512 { uint32_t w[16]; };

extern const u256 P256_P;
extern const u256 P256_R2;

// -------- low-level helpers --------
void u256_zero(u256& a);
bool u256_is_zero(const u256& a);
int  u256_cmp(const u256& a, const u256& b);

// raw ops (no mod reduction)
u256 u256_add_raw(const u256& a, const u256& b, uint32_t& carry);
u256 u256_sub_raw(const u256& a, const u256& b, uint32_t& borrow);

// conversion (network BE <-> internal LE)
u256 u256_from_be(const uint8_t be32[32]);
void u256_to_be(uint8_t out32[32], const u256& a);
u512 u512_from_be(const uint8_t in[64]);

// multiply
u512 u256_mul_raw(const u256& a, const u256& b);

// Montgomery core
u256 mont_mul(const u256& a, const u256& b);
u256 to_mont(const u256& a);
u256 from_mont(const u256& a);

// field ops (Montgomery domain)
u256 fp_add(const u256& a, const u256& b);
u256 fp_sub(const u256& a, const u256& b);
u256 fp_mul(const u256& a, const u256& b);
u256 fp_sqr(const u256& a);

// inversion (works with Montgomery domain inputs)
u256 fp_inv(const u256& a);

// scalar bit access
uint32_t get_bit_be(const uint8_t priv[32], int bit_index);

// reduction (512 -> 256 mod P)
u256 mod_p256(const u512& in);

} // namespace netplus