Source tree reorganization: - Move StarForth v3 engine to v3/ (src/, include/, Makefile) - Move kernel to kernel/ (src/, include/, linker/, Makefile) - Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md) - Move FABRIC-0..4.md to docs/fabric/ - Move ONTOLOGY.md and ROADMAP.md to docs/ Board infrastructure: - Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/ - Each board has board.mk (ISA, CPU flags, boot recipe) and README.md - Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET - make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board - ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet) - scripts/mkdiskimage.sh builds disk images for all boards Docs pipeline: - docs/book/ with LaTeX master (main.tex) and Makefile - pandoc converts Markdown to LaTeX at build time - Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks) - make docs builds single PDF (754 pages, 0 missing characters) - make docs TARGET=<board> adds board appendix - build/docs/<book|board>/meta.tex stamps git commit into PDF Bug fixes: - 42 include paths that only worked by accident now use correct relative paths - clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build) - Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved - Doxyfile, .clang-tidy, README.md, Kconfig paths updated Verified: - Hosted v3 build passes 1012 tests, 0 failures - SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE) - Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes - make clean TARGET=<board> removes only that board and its ISA objects - make all builds all boards, hosted v3, and docs in one run Co-authored-by: Junie <junie@jetbrains.com>
328 lines
10 KiB
C
328 lines
10 KiB
C
/* ed25519.c -- see ed25519.h. Twisted Edwards curve point arithmetic
|
|
* (projective X:Y:Z coordinates, unified addition law -- correctness
|
|
* over speed, this is a verify-only, non-hot-path library) plus the
|
|
* RFC 8032 EdDSA verify algorithm.
|
|
*
|
|
* All constants below were generated by direct Python computation
|
|
* (arbitrary-precision arithmetic) and pasted in, not hand-derived or
|
|
* recalled from memory -- see FABRIC-1.md's Ed25519 milestone writeup
|
|
* for the derivation. Cross-checked: the curve equation
|
|
* -x^2+y^2 = 1+d*x^2*y^2 holds for (BX,BY) under this D; the base point
|
|
* encoding (0x58, then 0x66 repeating) matches the well-known published
|
|
* Ed25519 base point; L (scalar25519.c) independently confirmed prime.
|
|
*/
|
|
#include "starkernel/ed25519.h"
|
|
#include "starkernel/fe25519.h"
|
|
#include "starkernel/scalar25519.h"
|
|
#include "starkernel/sha512.h"
|
|
#include <string.h>
|
|
|
|
static const uint8_t D_BYTES[32] = {
|
|
0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75,
|
|
0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00,
|
|
0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c,
|
|
0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52,
|
|
};
|
|
static const uint8_t BX_BYTES[32] = {
|
|
0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9,
|
|
0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69,
|
|
0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0,
|
|
0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21,
|
|
};
|
|
static const uint8_t BY_BYTES[32] = {
|
|
0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
|
};
|
|
static const uint8_t SQRTM1_BYTES[32] = {
|
|
0xb0, 0xa0, 0x0e, 0x4a, 0x27, 0x1b, 0xee, 0xc4,
|
|
0x78, 0xe4, 0x2f, 0xad, 0x06, 0x18, 0x43, 0x2f,
|
|
0xa7, 0xd7, 0xfb, 0x3d, 0x99, 0x00, 0x4d, 0x2b,
|
|
0x0b, 0xdf, 0xc1, 0x4f, 0x80, 0x24, 0x83, 0x2b,
|
|
};
|
|
static const uint8_t POW_2252M2_EXP[32] = {
|
|
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f,
|
|
};
|
|
|
|
typedef struct { fe25519 x, y, z; } ed_point_t;
|
|
|
|
static void fe25519_pow(fe25519 *r, const fe25519 *a, const uint8_t exp[32])
|
|
{
|
|
fe25519 result; fe25519_1(&result);
|
|
for (int i = 255; i >= 0; i--) {
|
|
fe25519_sq(&result, &result);
|
|
if ((exp[i/8] >> (i%8)) & 1) {
|
|
fe25519_mul(&result, &result, a);
|
|
}
|
|
}
|
|
*r = result;
|
|
}
|
|
|
|
static void point_identity(ed_point_t *p)
|
|
{
|
|
fe25519_0(&p->x);
|
|
fe25519_1(&p->y);
|
|
fe25519_1(&p->z);
|
|
}
|
|
|
|
/* Unified addition law for twisted Edwards curves with a=-1 (Ed25519's
|
|
* curve), projective coordinates -- works for both general addition and
|
|
* doubling (p1==p2), avoiding a separate doubling formula and its own
|
|
* chance of transcription error. Standard "add-2008-bbjlp"-family
|
|
* formula (add_bbjlp_readdition in EFD terminology), not invented here. */
|
|
static void point_add(ed_point_t *r, const ed_point_t *p1, const ed_point_t *p2)
|
|
{
|
|
fe25519 d; fe25519_unpack(&d, D_BYTES);
|
|
|
|
fe25519 A, B, C, Dd, E, F, G, t1, t2;
|
|
fe25519_mul(&A, &p1->z, &p2->z);
|
|
fe25519_sq(&B, &A);
|
|
fe25519_mul(&C, &p1->x, &p2->x);
|
|
fe25519_mul(&Dd, &p1->y, &p2->y);
|
|
fe25519_mul(&E, &d, &C);
|
|
fe25519_mul(&E, &E, &Dd);
|
|
fe25519_sub(&F, &B, &E);
|
|
fe25519_add(&G, &B, &E);
|
|
|
|
fe25519_add(&t1, &p1->x, &p1->y);
|
|
fe25519_add(&t2, &p2->x, &p2->y);
|
|
fe25519_mul(&t1, &t1, &t2);
|
|
fe25519_sub(&t1, &t1, &C);
|
|
fe25519_sub(&t1, &t1, &Dd);
|
|
fe25519_mul(&t1, &t1, &F);
|
|
fe25519_mul(&r->x, &A, &t1);
|
|
|
|
fe25519_add(&t2, &Dd, &C);
|
|
fe25519_mul(&t2, &t2, &G);
|
|
fe25519_mul(&r->y, &A, &t2);
|
|
|
|
fe25519_mul(&r->z, &F, &G);
|
|
}
|
|
|
|
/* Non-constant-time double-and-add: fine here -- verify's scalars (a
|
|
* message hash and the signature's S component) are public data, not
|
|
* secrets, so there is no timing side channel to defend against (see
|
|
* ed25519.h's own doc comment). scalar is 32 bytes little-endian. */
|
|
static void scalar_mult(ed_point_t *r, const uint8_t scalar[32], const ed_point_t *p)
|
|
{
|
|
ed_point_t result;
|
|
point_identity(&result);
|
|
for (int i = 255; i >= 0; i--) {
|
|
point_add(&result, &result, &result);
|
|
if ((scalar[i/8] >> (i%8)) & 1) {
|
|
point_add(&result, &result, p);
|
|
}
|
|
}
|
|
*r = result;
|
|
}
|
|
|
|
/* Decompress a 32-byte encoded point: byte 31's top bit is x's sign;
|
|
* the rest (255 bits) is y. Recovers x via the curve equation and a
|
|
* modular square root (p = 5 mod 8, so a candidate root is
|
|
* u^((p+3)/8) mod p, corrected by sqrt(-1) if the first candidate's
|
|
* square doesn't match -- the standard technique for this prime shape).
|
|
* Returns 0 on success, -1 if the encoded value has no valid x (not a
|
|
* point on the curve) -- a malformed/malicious input, must be rejected,
|
|
* not faulted on. */
|
|
static int point_decompress(ed_point_t *p, const uint8_t in[32])
|
|
{
|
|
int sign = (in[31] >> 7) & 1;
|
|
uint8_t ybytes[32];
|
|
memcpy(ybytes, in, 32);
|
|
ybytes[31] &= 0x7F;
|
|
|
|
fe25519 y, d, one, y2, u, v, x2, x, cand, sqrt_m1, check;
|
|
fe25519_unpack(&y, ybytes);
|
|
fe25519_unpack(&d, D_BYTES);
|
|
fe25519_1(&one);
|
|
|
|
fe25519_sq(&y2, &y);
|
|
fe25519_sub(&u, &y2, &one);
|
|
fe25519_mul(&v, &d, &y2);
|
|
fe25519_add(&v, &v, &one);
|
|
|
|
fe25519 vinv;
|
|
fe25519_invert(&vinv, &v);
|
|
fe25519_mul(&x2, &u, &vinv);
|
|
|
|
fe25519_pow(&cand, &x2, POW_2252M2_EXP);
|
|
fe25519_sq(&check, &cand);
|
|
if (!fe25519_eq(&check, &x2)) {
|
|
fe25519_unpack(&sqrt_m1, SQRTM1_BYTES);
|
|
fe25519_mul(&cand, &cand, &sqrt_m1);
|
|
fe25519_sq(&check, &cand);
|
|
if (!fe25519_eq(&check, &x2)) {
|
|
return -1; /* x2 is not a square mod p -- not a valid point */
|
|
}
|
|
}
|
|
x = cand;
|
|
if (fe25519_parity(&x) != sign) {
|
|
fe25519_neg(&x, &x);
|
|
}
|
|
/* Reject the one remaining edge case RFC 8032 calls out: x == 0
|
|
* with sign bit set to 1 has no valid representative. */
|
|
fe25519 zero; fe25519_0(&zero);
|
|
if (fe25519_eq(&x, &zero) && sign) {
|
|
return -1;
|
|
}
|
|
|
|
p->x = x;
|
|
p->y = y;
|
|
fe25519_1(&p->z);
|
|
return 0;
|
|
}
|
|
|
|
static void point_compress(uint8_t out[32], const ed_point_t *p)
|
|
{
|
|
fe25519 zinv, x, y;
|
|
fe25519_invert(&zinv, &p->z);
|
|
fe25519_mul(&x, &p->x, &zinv);
|
|
fe25519_mul(&y, &p->y, &zinv);
|
|
fe25519_pack(out, &y);
|
|
if (fe25519_parity(&x)) {
|
|
out[31] |= 0x80;
|
|
}
|
|
}
|
|
|
|
/* RFC 8032 5.1.5 clamping: forces the scalar into the prime-order
|
|
* subgroup and sets the high bit for a fixed exponent bit-length,
|
|
* defending against a small class of implementation attacks on other
|
|
* verifiers -- not needed for correctness against a correct verifier,
|
|
* required by the spec regardless. */
|
|
static void clamp_scalar(uint8_t a[32])
|
|
{
|
|
a[0] &= 248;
|
|
a[31] &= 127;
|
|
a[31] |= 64;
|
|
}
|
|
|
|
/* h = SHA512(seed); a = clamp(h[0:32]) (the actual signing scalar);
|
|
* prefix = h[32:64] (mixed into the per-message nonce, RFC 8032 5.1.6
|
|
* step 2). Shared by keygen (only needs a) and sign (needs both). */
|
|
static void expand_seed(const uint8_t seed[32], uint8_t a[32], uint8_t prefix[32])
|
|
{
|
|
uint8_t h[64];
|
|
sha512(seed, 32, h);
|
|
memcpy(a, h, 32);
|
|
clamp_scalar(a);
|
|
memcpy(prefix, h + 32, 32);
|
|
}
|
|
|
|
static void base_point(ed_point_t *b)
|
|
{
|
|
fe25519_unpack(&b->x, BX_BYTES);
|
|
fe25519_unpack(&b->y, BY_BYTES);
|
|
fe25519_1(&b->z);
|
|
}
|
|
|
|
void ed25519_keygen(const uint8_t seed[32], uint8_t pubkey_out[32])
|
|
{
|
|
uint8_t a[32], prefix[32];
|
|
expand_seed(seed, a, prefix);
|
|
|
|
ed_point_t B, A;
|
|
base_point(&B);
|
|
scalar_mult(&A, a, &B);
|
|
point_compress(pubkey_out, &A);
|
|
}
|
|
|
|
void ed25519_sign(const uint8_t seed[32], const uint8_t *msg, size_t msg_len,
|
|
uint8_t sig_out[64])
|
|
{
|
|
uint8_t a[32], prefix[32];
|
|
expand_seed(seed, a, prefix);
|
|
|
|
ed_point_t B, A;
|
|
base_point(&B);
|
|
scalar_mult(&A, a, &B);
|
|
uint8_t A_enc[32];
|
|
point_compress(A_enc, &A);
|
|
|
|
/* r = SHA512(prefix || M) mod L -- the per-message nonce. Derived,
|
|
* not random: this is what makes EdDSA signing deterministic and
|
|
* needs no entropy at sign time (only expand_seed's one-time keygen
|
|
* step ever touches real randomness, via the caller-supplied seed). */
|
|
sha512_ctx_t ctx;
|
|
sha512_init(&ctx);
|
|
sha512_update(&ctx, prefix, 32);
|
|
sha512_update(&ctx, msg, msg_len);
|
|
uint8_t r_hash[64];
|
|
sha512_final(&ctx, r_hash);
|
|
uint8_t r[32];
|
|
scalar_reduce512(r, r_hash);
|
|
|
|
ed_point_t R;
|
|
scalar_mult(&R, r, &B);
|
|
uint8_t R_enc[32];
|
|
point_compress(R_enc, &R);
|
|
|
|
/* k = SHA512(R || A || M) mod L -- identical shape to verify's own
|
|
* k computation above, necessarily: a valid signature must satisfy
|
|
* exactly the equation verify checks. */
|
|
sha512_init(&ctx);
|
|
sha512_update(&ctx, R_enc, 32);
|
|
sha512_update(&ctx, A_enc, 32);
|
|
sha512_update(&ctx, msg, msg_len);
|
|
uint8_t k_hash[64];
|
|
sha512_final(&ctx, k_hash);
|
|
uint8_t k[32];
|
|
scalar_reduce512(k, k_hash);
|
|
|
|
/* S = (k*a + r) mod L */
|
|
uint8_t S[32];
|
|
scalar_muladd(S, k, a, r);
|
|
|
|
memcpy(sig_out, R_enc, 32);
|
|
memcpy(sig_out + 32, S, 32);
|
|
}
|
|
|
|
int ed25519_verify(const uint8_t pubkey[32], const uint8_t *msg, size_t msg_len,
|
|
const uint8_t sig[64])
|
|
{
|
|
const uint8_t *R_bytes = sig;
|
|
const uint8_t *S_bytes = sig + 32;
|
|
|
|
/* RFC 8032 malleability requirement: reject S >= L outright, not
|
|
* just S >= 2^256 -- this is exactly the check a naive
|
|
* "treat S as a 256-bit number" implementation would skip. */
|
|
if (!scalar_lt_L(S_bytes)) return 0;
|
|
|
|
ed_point_t A, R;
|
|
if (point_decompress(&A, pubkey) != 0) return 0;
|
|
if (point_decompress(&R, R_bytes) != 0) return 0;
|
|
|
|
/* k = SHA512(R || A || M) mod L */
|
|
sha512_ctx_t ctx;
|
|
sha512_init(&ctx);
|
|
sha512_update(&ctx, R_bytes, 32);
|
|
sha512_update(&ctx, pubkey, 32);
|
|
sha512_update(&ctx, msg, msg_len);
|
|
uint8_t hash[64];
|
|
sha512_final(&ctx, hash);
|
|
uint8_t k[32];
|
|
scalar_reduce512(k, hash);
|
|
|
|
/* Check [S]B == R + [k]A by comparing compressed encodings of both
|
|
* sides -- simpler and just as correct as an in-projective-
|
|
* coordinates equality check for a verify-only, non-hot-path
|
|
* library. */
|
|
ed_point_t B, sB, kA, rhs;
|
|
fe25519_unpack(&B.x, BX_BYTES);
|
|
fe25519_unpack(&B.y, BY_BYTES);
|
|
fe25519_1(&B.z);
|
|
|
|
scalar_mult(&sB, S_bytes, &B);
|
|
scalar_mult(&kA, k, &A);
|
|
point_add(&rhs, &R, &kA);
|
|
|
|
uint8_t lhs_enc[32], rhs_enc[32];
|
|
point_compress(lhs_enc, &sB);
|
|
point_compress(rhs_enc, &rhs);
|
|
|
|
return memcmp(lhs_enc, rhs_enc, 32) == 0;
|
|
}
|