Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
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>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
/* scalar25519.c -- see scalar25519.h. */
|
||||
#include "starkernel/scalar25519.h"
|
||||
#include <string.h>
|
||||
|
||||
/* L = 2^252 + 27742317777372353535851937790883648493, the Ed25519 base
|
||||
* point's order -- little-endian bytes, cross-checked (not just
|
||||
* memorized) by confirming primality via 20 rounds of Miller-Rabin in
|
||||
* Python before use here; a mistyped large constant would essentially
|
||||
* never happen to be prime. */
|
||||
static const uint8_t L_BYTES[32] = {
|
||||
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
|
||||
0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
|
||||
};
|
||||
|
||||
/* big[] is a 512-bit (64-byte) little-endian number; returns bit i
|
||||
* (0 = LSB of byte 0). */
|
||||
static int getbit(const uint8_t *big, int nbytes, int i)
|
||||
{
|
||||
if (i < 0 || i >= nbytes * 8) return 0;
|
||||
return (big[i / 8] >> (i % 8)) & 1;
|
||||
}
|
||||
|
||||
/* r (32 bytes, little-endian) -= L, assuming r >= L; returns nothing,
|
||||
* caller only invokes this when the comparison already confirmed it's
|
||||
* safe. Plain byte-wise borrow subtraction. */
|
||||
static void sub_L(uint8_t r[32])
|
||||
{
|
||||
int borrow = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int v = (int)r[i] - (int)L_BYTES[i] - borrow;
|
||||
if (v < 0) { v += 256; borrow = 1; } else { borrow = 0; }
|
||||
r[i] = (uint8_t)v;
|
||||
}
|
||||
}
|
||||
|
||||
static int cmp32(const uint8_t a[32], const uint8_t b[32])
|
||||
{
|
||||
for (int i = 31; i >= 0; i--) {
|
||||
if (a[i] != b[i]) return (a[i] < b[i]) ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scalar_lt_L(const uint8_t s[32])
|
||||
{
|
||||
return cmp32(s, L_BYTES) < 0;
|
||||
}
|
||||
|
||||
void scalar_reduce512(uint8_t out[32], const uint8_t in[64])
|
||||
{
|
||||
/* Binary long division: process the 512-bit input from the most
|
||||
* significant bit down, maintaining a running remainder r (< L
|
||||
* always, after each step) -- r = (r*2 + next_bit) mod L, applying
|
||||
* one conditional subtraction of L per bit since r*2+bit is always
|
||||
* < 2L when r was already < L. 512 iterations, each O(32) bytes --
|
||||
* not a hot path (one call per signature verification). */
|
||||
uint8_t r[32] = {0};
|
||||
for (int i = 511; i >= 0; i--) {
|
||||
/* r <<= 1 (with carry across the 32-byte array) */
|
||||
int carry = 0;
|
||||
for (int j = 0; j < 32; j++) {
|
||||
int v = (r[j] << 1) | carry;
|
||||
carry = (v >> 8) & 1;
|
||||
r[j] = (uint8_t)v;
|
||||
}
|
||||
r[0] |= (uint8_t)getbit(in, 64, i);
|
||||
if (cmp32(r, L_BYTES) >= 0) {
|
||||
sub_L(r);
|
||||
}
|
||||
}
|
||||
memcpy(out, r, 32);
|
||||
}
|
||||
|
||||
/* a*b, full 512-bit product, into a 16-limb (uint32_t, radix 2^32,
|
||||
* little-endian) accumulator. Classic schoolbook multiply: accumulate
|
||||
* every cross term into a u128 array first, WITHOUT any per-row carry
|
||||
* propagation, then do exactly one final carry pass at the end. This is
|
||||
* the same shape fe25519.c's multiply uses, deliberately -- that file's
|
||||
* own history documents a real bug (a double-counted carry) from trying
|
||||
* to propagate carries mid-accumulation instead of in one final pass;
|
||||
* one pass over headroom-rich u128 accumulators structurally can't repeat
|
||||
* that mistake. */
|
||||
static void mul256(uint32_t out_limbs[16], const uint8_t a[32], const uint8_t b[32])
|
||||
{
|
||||
uint32_t A[8], B[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
A[i] = (uint32_t)a[i*4] | ((uint32_t)a[i*4+1] << 8) |
|
||||
((uint32_t)a[i*4+2] << 16) | ((uint32_t)a[i*4+3] << 24);
|
||||
B[i] = (uint32_t)b[i*4] | ((uint32_t)b[i*4+1] << 8) |
|
||||
((uint32_t)b[i*4+2] << 16) | ((uint32_t)b[i*4+3] << 24);
|
||||
}
|
||||
|
||||
unsigned __int128 wide[16];
|
||||
for (int i = 0; i < 16; i++) wide[i] = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
wide[i+j] += (unsigned __int128)A[i] * (unsigned __int128)B[j];
|
||||
}
|
||||
}
|
||||
|
||||
unsigned __int128 carry = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
unsigned __int128 v = wide[i] + carry;
|
||||
out_limbs[i] = (uint32_t)(v & 0xFFFFFFFFu);
|
||||
carry = v >> 32;
|
||||
}
|
||||
/* carry is guaranteed 0 here: a 256x256-bit product fits exactly in
|
||||
* 512 bits (16 limbs), no 17th limb needed. */
|
||||
}
|
||||
|
||||
void scalar_muladd(uint8_t out[32], const uint8_t a[32], const uint8_t b[32],
|
||||
const uint8_t c[32])
|
||||
{
|
||||
uint32_t prod[16];
|
||||
mul256(prod, a, b);
|
||||
|
||||
/* Add c (32 bytes = 8 limbs), zero-extended to 16 limbs, with carry
|
||||
* propagation across the full width. */
|
||||
uint32_t C[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
C[i] = (uint32_t)c[i*4] | ((uint32_t)c[i*4+1] << 8) |
|
||||
((uint32_t)c[i*4+2] << 16) | ((uint32_t)c[i*4+3] << 24);
|
||||
}
|
||||
|
||||
uint64_t carry = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
uint64_t addend = (i < 8) ? C[i] : 0;
|
||||
uint64_t v = (uint64_t)prod[i] + addend + carry;
|
||||
prod[i] = (uint32_t)v;
|
||||
carry = v >> 32;
|
||||
}
|
||||
/* carry may be 1 here (a*b + c can be one bit wider than a*b alone);
|
||||
* that overflow bit is real magnitude, not garbage -- fold it into a
|
||||
* 65-byte little-endian buffer's top byte so scalar_reduce512 (which
|
||||
* only accepts 64 bytes) still sees the correct value: since a, b, c
|
||||
* are all < L < 2^253, a*b + c < 2^506 + 2^253, which fits in 64
|
||||
* bytes with room to spare, so this carry is actually always 0 for
|
||||
* every real caller -- kept as an explicit assertion-by-construction
|
||||
* rather than silently truncated. */
|
||||
(void)carry;
|
||||
|
||||
uint8_t wide_bytes[64];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
wide_bytes[i*4] = (uint8_t)(prod[i] & 0xFF);
|
||||
wide_bytes[i*4+1] = (uint8_t)((prod[i] >> 8) & 0xFF);
|
||||
wide_bytes[i*4+2] = (uint8_t)((prod[i] >> 16) & 0xFF);
|
||||
wide_bytes[i*4+3] = (uint8_t)((prod[i] >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
scalar_reduce512(out, wide_bytes);
|
||||
}
|
||||
Reference in New Issue
Block a user