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,327 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/* fe25519.c -- see fe25519.h for the representation and why. */
|
||||
#include "starkernel/fe25519.h"
|
||||
#include <string.h>
|
||||
|
||||
typedef unsigned __int128 u128;
|
||||
|
||||
#define WBITS 51
|
||||
#define MASK51 (((int64_t)1 << WBITS) - 1)
|
||||
|
||||
void fe25519_0(fe25519 *r) { memset(r->v, 0, sizeof(r->v)); }
|
||||
|
||||
void fe25519_1(fe25519 *r) { memset(r->v, 0, sizeof(r->v)); r->v[0] = 1; }
|
||||
|
||||
void fe25519_copy(fe25519 *r, const fe25519 *a) { *r = *a; }
|
||||
|
||||
/* Carry-propagate v[] in place, base 2^51, 5 limbs; the carry out of
|
||||
* limb 4 wraps back into limb 0 multiplied by 19 (2^255 = 19 mod p, and
|
||||
* 51*5 = 255 exactly, so this is the plain reduction constant -- no
|
||||
* extra scaling needed, unlike a limb count/width that doesn't divide
|
||||
* 255 evenly). Two passes: the wraparound carry can itself need to
|
||||
* ripple through limb 0 again on rare inputs. Works for negative limbs
|
||||
* too (arithmetic shift preserves sign) -- needed after fe25519_sub. */
|
||||
static void fe25519_carry(fe25519 *r)
|
||||
{
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int64_t c = r->v[i] >> WBITS;
|
||||
r->v[i] -= c << WBITS;
|
||||
r->v[i+1] += c;
|
||||
}
|
||||
int64_t c = r->v[4] >> WBITS;
|
||||
r->v[4] -= c << WBITS;
|
||||
r->v[0] += c * 19;
|
||||
}
|
||||
}
|
||||
|
||||
void fe25519_add(fe25519 *r, const fe25519 *a, const fe25519 *b)
|
||||
{
|
||||
for (int i = 0; i < 5; i++) r->v[i] = a->v[i] + b->v[i];
|
||||
fe25519_carry(r);
|
||||
}
|
||||
|
||||
void fe25519_sub(fe25519 *r, const fe25519 *a, const fe25519 *b)
|
||||
{
|
||||
for (int i = 0; i < 5; i++) r->v[i] = a->v[i] - b->v[i];
|
||||
fe25519_carry(r);
|
||||
}
|
||||
|
||||
void fe25519_neg(fe25519 *r, const fe25519 *a)
|
||||
{
|
||||
fe25519 zero; fe25519_0(&zero);
|
||||
fe25519_sub(r, &zero, a);
|
||||
}
|
||||
|
||||
/* Schoolbook multiply into a 9-element __int128 wide accumulator (each
|
||||
* product a[i]*b[j] is at most ~51+51=102 bits; a bucket sums at most 5
|
||||
* such products, at most ~105 bits -- __int128 has ~127 bits of range,
|
||||
* comfortable headroom, confirmed this kernel's toolchains compile
|
||||
* __int128 multiply/add/shift-by-constant with no libgcc calls -- see
|
||||
* fe25519.h's own doc comment). Fold positions 5-8 into 0-3 (2^255 = 19
|
||||
* mod p, and since 51*5=255 exactly, position k for k=5..8 represents
|
||||
* 2^(255+51*(k-5)) = 19 * 2^(51*(k-5)) mod p, i.e. folds directly into
|
||||
* position k-5 with the plain multiplier 19 -- no extra scale factor,
|
||||
* unlike a limb width that doesn't divide 255 evenly). */
|
||||
void fe25519_mul(fe25519 *r, const fe25519 *a, const fe25519 *b)
|
||||
{
|
||||
u128 wide[9] = {0};
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 5; j++) {
|
||||
wide[i+j] += (u128)(uint64_t)a->v[i] * (uint64_t)b->v[j];
|
||||
}
|
||||
}
|
||||
for (int k = 8; k >= 5; k--) {
|
||||
wide[k-5] += 19 * wide[k];
|
||||
}
|
||||
/* wide[0..4] can each exceed 51 (even 63) bits after folding --
|
||||
* carry-propagate using __int128 arithmetic (roomy enough that no
|
||||
* intermediate step here risks overflow) before narrowing to the
|
||||
* int64_t storage representation. */
|
||||
u128 carry = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
u128 full = wide[i] + carry;
|
||||
r->v[i] = (int64_t)(full & (u128)MASK51);
|
||||
carry = full >> WBITS;
|
||||
}
|
||||
r->v[0] += (int64_t)(carry * 19);
|
||||
fe25519_carry(r);
|
||||
}
|
||||
|
||||
void fe25519_sq(fe25519 *r, const fe25519 *a)
|
||||
{
|
||||
fe25519_mul(r, a, a);
|
||||
}
|
||||
|
||||
void fe25519_mul_small(fe25519 *r, const fe25519 *a, uint32_t c)
|
||||
{
|
||||
u128 wide[5];
|
||||
for (int i = 0; i < 5; i++) wide[i] = (u128)(uint64_t)a->v[i] * c;
|
||||
u128 carry = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
u128 full = wide[i] + carry;
|
||||
r->v[i] = (int64_t)(full & (u128)MASK51);
|
||||
carry = full >> WBITS;
|
||||
}
|
||||
r->v[0] += (int64_t)(carry * 19);
|
||||
fe25519_carry(r);
|
||||
}
|
||||
|
||||
/* Modular inverse via Fermat's little theorem: a^(p-2) mod p, using the
|
||||
* standard Ed25519 addition chain (222 squarings + 24 extra multiplies)
|
||||
* -- the same exponent decomposition every Ed25519 reference
|
||||
* implementation uses, not something invented here; verified by the
|
||||
* property-based host test (a * inverse(a) == 1) rather than trusted by
|
||||
* inspection. */
|
||||
void fe25519_invert(fe25519 *r, const fe25519 *a)
|
||||
{
|
||||
fe25519 z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t;
|
||||
|
||||
fe25519_sq(&z2, a); /* 2 */
|
||||
fe25519_sq(&t, &z2); /* 4 */
|
||||
fe25519_sq(&t, &t); /* 8 */
|
||||
fe25519_mul(&z9, &t, a); /* 9 */
|
||||
fe25519_mul(&z11, &z9, &z2); /* 11 */
|
||||
fe25519_sq(&t, &z11); /* 22 */
|
||||
fe25519_mul(&z2_5_0, &t, &z9); /* 2^5 - 2^0 = 31 */
|
||||
|
||||
fe25519_sq(&t, &z2_5_0);
|
||||
for (int i = 1; i < 5; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&z2_10_0, &t, &z2_5_0); /* 2^10 - 2^0 */
|
||||
|
||||
fe25519_sq(&t, &z2_10_0);
|
||||
for (int i = 1; i < 10; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&z2_20_0, &t, &z2_10_0); /* 2^20 - 2^0 */
|
||||
|
||||
fe25519_sq(&t, &z2_20_0);
|
||||
for (int i = 1; i < 20; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&t, &t, &z2_20_0); /* 2^40 - 2^0 */
|
||||
|
||||
for (int i = 0; i < 10; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&z2_50_0, &t, &z2_10_0); /* 2^50 - 2^0 */
|
||||
|
||||
fe25519_sq(&t, &z2_50_0);
|
||||
for (int i = 1; i < 50; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&z2_100_0, &t, &z2_50_0); /* 2^100 - 2^0 */
|
||||
|
||||
fe25519_sq(&t, &z2_100_0);
|
||||
for (int i = 1; i < 100; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&t, &t, &z2_100_0); /* 2^200 - 2^0 */
|
||||
|
||||
for (int i = 0; i < 50; i++) fe25519_sq(&t, &t);
|
||||
fe25519_mul(&t, &t, &z2_50_0); /* 2^250 - 2^0 */
|
||||
|
||||
fe25519_sq(&t, &t);
|
||||
fe25519_sq(&t, &t);
|
||||
fe25519_sq(&t, &t);
|
||||
fe25519_sq(&t, &t);
|
||||
fe25519_sq(&t, &t); /* 2^255 - 2^5 */
|
||||
fe25519_mul(r, &t, &z11); /* 2^255 - 21 = p - 2 */
|
||||
}
|
||||
|
||||
/* Fully reduce a's limbs to a unique representative in [0, p), then pack
|
||||
* little-endian into 32 bytes. */
|
||||
void fe25519_pack(uint8_t out[32], const fe25519 *a)
|
||||
{
|
||||
fe25519 t = *a;
|
||||
fe25519_carry(&t);
|
||||
|
||||
/* Force full reduction: subtract p once if t >= p, by subtracting
|
||||
* p's exact base-2^51 representation (limb0 = 2^51-19, limbs 1-4 =
|
||||
* 2^51-1, computed directly, not memorized) and checking whether
|
||||
* that needed a borrow out of the top -- if not, t was already >= p
|
||||
* and the subtracted value is the reduced one; if so, t was already
|
||||
* < p. Not constant-time; fine here since packing only ever happens
|
||||
* on public data (a verified signature's derived point, never a
|
||||
* secret) in this verify-only library. */
|
||||
static const int64_t P_LIMBS[5] = {
|
||||
MASK51 - 18, MASK51, MASK51, MASK51, MASK51
|
||||
};
|
||||
int64_t borrow = 0;
|
||||
int64_t rawlimb[5];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int64_t v = t.v[i] - P_LIMBS[i] - borrow;
|
||||
if (v < 0) {
|
||||
v += (int64_t)1 << WBITS;
|
||||
borrow = 1;
|
||||
} else {
|
||||
borrow = 0;
|
||||
}
|
||||
rawlimb[i] = v;
|
||||
}
|
||||
if (!borrow) {
|
||||
for (int i = 0; i < 5; i++) t.v[i] = rawlimb[i];
|
||||
}
|
||||
|
||||
uint64_t bits = 0;
|
||||
int bitcnt = 0;
|
||||
int outpos = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
bits |= ((uint64_t)(uint64_t)t.v[i]) << bitcnt;
|
||||
bitcnt += WBITS;
|
||||
while (bitcnt >= 8) {
|
||||
out[outpos++] = (uint8_t)bits;
|
||||
bits >>= 8;
|
||||
bitcnt -= 8;
|
||||
}
|
||||
}
|
||||
if (bitcnt > 0 && outpos < 32) {
|
||||
out[outpos++] = (uint8_t)bits;
|
||||
}
|
||||
while (outpos < 32) out[outpos++] = 0;
|
||||
}
|
||||
|
||||
void fe25519_unpack(fe25519 *r, const uint8_t in[32])
|
||||
{
|
||||
uint64_t bits = 0;
|
||||
int bitcnt = 0;
|
||||
int inpos = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
while (bitcnt < WBITS && inpos < 32) {
|
||||
bits |= ((uint64_t)in[inpos++]) << bitcnt;
|
||||
bitcnt += 8;
|
||||
}
|
||||
r->v[i] = (int64_t)(bits & (uint64_t)MASK51);
|
||||
bits >>= WBITS;
|
||||
bitcnt -= WBITS;
|
||||
}
|
||||
/* Bit 255 of the input (the top bit of byte 31) is Ed25519's point-
|
||||
* encoding sign bit, not part of y's magnitude -- caller (point
|
||||
* decompression) masks it out before calling this. Limb 4 here only
|
||||
* ever receives up to 51 significant unpacked bits from a 255-bit
|
||||
* input by construction (4*51=204, 255-204=51), so no explicit mask
|
||||
* against a stray bit 255 is needed as long as the caller has
|
||||
* already cleared it in the input bytes. */
|
||||
}
|
||||
|
||||
int fe25519_eq(const fe25519 *a, const fe25519 *b)
|
||||
{
|
||||
uint8_t pa[32], pb[32];
|
||||
fe25519_pack(pa, a);
|
||||
fe25519_pack(pb, b);
|
||||
return memcmp(pa, pb, 32) == 0;
|
||||
}
|
||||
|
||||
int fe25519_parity(const fe25519 *a)
|
||||
{
|
||||
uint8_t p[32];
|
||||
fe25519_pack(p, a);
|
||||
return p[0] & 1;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/* sha512.c -- FIPS 180-4 SHA-512, freestanding C99, 64-bit words only. */
|
||||
#include "starkernel/sha512.h"
|
||||
#include <string.h>
|
||||
|
||||
static const uint64_t K[80] = {
|
||||
0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
|
||||
0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
|
||||
0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
|
||||
0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
|
||||
0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
|
||||
0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
|
||||
0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
|
||||
0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
|
||||
0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
|
||||
0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
|
||||
0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
|
||||
0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
|
||||
0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
|
||||
0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
|
||||
0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
|
||||
0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
|
||||
0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
|
||||
0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
|
||||
0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
|
||||
0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
|
||||
};
|
||||
|
||||
static inline uint64_t rotr64(uint64_t x, int n) { return (x >> n) | (x << (64 - n)); }
|
||||
|
||||
static void sha512_block(sha512_ctx_t *ctx, const uint8_t block[128])
|
||||
{
|
||||
uint64_t w[80];
|
||||
for (int t = 0; t < 16; t++) {
|
||||
w[t] = ((uint64_t)block[t*8+0] << 56) | ((uint64_t)block[t*8+1] << 48) |
|
||||
((uint64_t)block[t*8+2] << 40) | ((uint64_t)block[t*8+3] << 32) |
|
||||
((uint64_t)block[t*8+4] << 24) | ((uint64_t)block[t*8+5] << 16) |
|
||||
((uint64_t)block[t*8+6] << 8) | ((uint64_t)block[t*8+7]);
|
||||
}
|
||||
for (int t = 16; t < 80; t++) {
|
||||
uint64_t s0 = rotr64(w[t-15], 1) ^ rotr64(w[t-15], 8) ^ (w[t-15] >> 7);
|
||||
uint64_t s1 = rotr64(w[t-2], 19) ^ rotr64(w[t-2], 61) ^ (w[t-2] >> 6);
|
||||
w[t] = w[t-16] + s0 + w[t-7] + s1;
|
||||
}
|
||||
|
||||
uint64_t a = ctx->state[0], b = ctx->state[1], c = ctx->state[2], d = ctx->state[3];
|
||||
uint64_t e = ctx->state[4], f = ctx->state[5], g = ctx->state[6], h = ctx->state[7];
|
||||
|
||||
for (int t = 0; t < 80; t++) {
|
||||
uint64_t S1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41);
|
||||
uint64_t ch = (e & f) ^ (~e & g);
|
||||
uint64_t t1 = h + S1 + ch + K[t] + w[t];
|
||||
uint64_t S0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39);
|
||||
uint64_t maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
uint64_t t2 = S0 + maj;
|
||||
h = g; g = f; f = e; e = d + t1;
|
||||
d = c; c = b; b = a; a = t1 + t2;
|
||||
}
|
||||
|
||||
ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
|
||||
ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
|
||||
}
|
||||
|
||||
void sha512_init(sha512_ctx_t *ctx)
|
||||
{
|
||||
ctx->state[0] = 0x6a09e667f3bcc908ULL;
|
||||
ctx->state[1] = 0xbb67ae8584caa73bULL;
|
||||
ctx->state[2] = 0x3c6ef372fe94f82bULL;
|
||||
ctx->state[3] = 0xa54ff53a5f1d36f1ULL;
|
||||
ctx->state[4] = 0x510e527fade682d1ULL;
|
||||
ctx->state[5] = 0x9b05688c2b3e6c1fULL;
|
||||
ctx->state[6] = 0x1f83d9abfb41bd6bULL;
|
||||
ctx->state[7] = 0x5be0cd19137e2179ULL;
|
||||
ctx->bitlen = 0;
|
||||
ctx->buf_len = 0;
|
||||
}
|
||||
|
||||
void sha512_update(sha512_ctx_t *ctx, const uint8_t *data, size_t len)
|
||||
{
|
||||
ctx->bitlen += (uint64_t)len * 8;
|
||||
while (len > 0) {
|
||||
size_t take = 128 - ctx->buf_len;
|
||||
if (take > len) take = len;
|
||||
memcpy(ctx->buf + ctx->buf_len, data, take);
|
||||
ctx->buf_len += take;
|
||||
data += take;
|
||||
len -= take;
|
||||
if (ctx->buf_len == 128) {
|
||||
sha512_block(ctx, ctx->buf);
|
||||
ctx->buf_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sha512_final(sha512_ctx_t *ctx, uint8_t out[64])
|
||||
{
|
||||
uint64_t bitlen = ctx->bitlen;
|
||||
uint8_t pad = 0x80;
|
||||
sha512_update(ctx, &pad, 1);
|
||||
|
||||
uint8_t zero = 0;
|
||||
while (ctx->buf_len != 112) {
|
||||
sha512_update(ctx, &zero, 1);
|
||||
}
|
||||
|
||||
/* 128-bit big-endian length in bits; high 64 bits are always 0 here
|
||||
* (documented limitation in sha512.h -- no message will ever approach
|
||||
* 2^64 bits in this kernel's use). */
|
||||
uint8_t lenbuf[16] = {0};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
lenbuf[15 - i] = (uint8_t)(bitlen >> (8 * i));
|
||||
}
|
||||
/* Bypass sha512_update's bitlen accounting for the length field itself
|
||||
* -- it's padding, not message content. */
|
||||
memcpy(ctx->buf + ctx->buf_len, lenbuf, 16);
|
||||
ctx->buf_len += 16;
|
||||
sha512_block(ctx, ctx->buf);
|
||||
ctx->buf_len = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
out[i*8+0] = (uint8_t)(ctx->state[i] >> 56);
|
||||
out[i*8+1] = (uint8_t)(ctx->state[i] >> 48);
|
||||
out[i*8+2] = (uint8_t)(ctx->state[i] >> 40);
|
||||
out[i*8+3] = (uint8_t)(ctx->state[i] >> 32);
|
||||
out[i*8+4] = (uint8_t)(ctx->state[i] >> 24);
|
||||
out[i*8+5] = (uint8_t)(ctx->state[i] >> 16);
|
||||
out[i*8+6] = (uint8_t)(ctx->state[i] >> 8);
|
||||
out[i*8+7] = (uint8_t)(ctx->state[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void sha512(const uint8_t *data, size_t len, uint8_t out[64])
|
||||
{
|
||||
sha512_ctx_t ctx;
|
||||
sha512_init(&ctx);
|
||||
sha512_update(&ctx, data, len);
|
||||
sha512_final(&ctx, out);
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/* x509_ed25519.c -- see x509_ed25519.h. */
|
||||
#include "starkernel/x509_ed25519.h"
|
||||
#include "starkernel/ed25519.h"
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *p;
|
||||
size_t len;
|
||||
} der_span_t;
|
||||
|
||||
/*
|
||||
* Parse a single DER TLV starting at *cursor (must be < limit). Handles
|
||||
* short-form length (< 128, one byte) and long-form length (0x80 | N,
|
||||
* followed by N big-endian length bytes, N in 1..4 -- a real X.509 cert
|
||||
* with extensions routinely exceeds the 127-byte short-form limit).
|
||||
* Rejects indefinite-length (0x80 alone) and anything claiming a length
|
||||
* that would run past limit -- every step is bounds-checked, malformed
|
||||
* input is refused, never faulted on.
|
||||
*
|
||||
* On success: advances *cursor past this whole TLV, returns 0, sets
|
||||
* *tag_out to the raw tag byte and *content_out to a span over just the
|
||||
* VALUE bytes (not the tag/length prefix).
|
||||
*/
|
||||
static int der_next(const uint8_t **cursor, const uint8_t *limit,
|
||||
uint8_t *tag_out, der_span_t *content_out) {
|
||||
const uint8_t *p = *cursor;
|
||||
if (p >= limit) return -1;
|
||||
uint8_t tag = *p++;
|
||||
|
||||
if (p >= limit) return -1;
|
||||
uint8_t len_byte = *p++;
|
||||
size_t len;
|
||||
if (len_byte & 0x80u) {
|
||||
uint8_t nbytes = (uint8_t)(len_byte & 0x7Fu);
|
||||
if (nbytes == 0 || nbytes > 4) return -1; /* indefinite-length or unreasonably large */
|
||||
if ((size_t)(limit - p) < nbytes) return -1;
|
||||
len = 0;
|
||||
for (uint8_t i = 0; i < nbytes; i++) len = (len << 8) | *p++;
|
||||
} else {
|
||||
len = len_byte;
|
||||
}
|
||||
if ((size_t)(limit - p) < len) return -1;
|
||||
|
||||
*tag_out = tag;
|
||||
content_out->p = p;
|
||||
content_out->len = len;
|
||||
*cursor = p + len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int x509_extract_ed25519_pubkey(const uint8_t *der, size_t der_len,
|
||||
uint8_t pubkey_out[32]) {
|
||||
if (!der || !pubkey_out) return -1;
|
||||
|
||||
const uint8_t *cur = der;
|
||||
const uint8_t *end = der + der_len;
|
||||
uint8_t tag;
|
||||
der_span_t cert;
|
||||
|
||||
/* Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue } */
|
||||
if (der_next(&cur, end, &tag, &cert) != 0 || tag != 0x30) return -1;
|
||||
|
||||
/* tbsCertificate ::= SEQUENCE { ... } -- first child of Certificate */
|
||||
const uint8_t *tbs_cur = cert.p;
|
||||
const uint8_t *tbs_limit = cert.p + cert.len;
|
||||
der_span_t tbs;
|
||||
if (der_next(&tbs_cur, tbs_limit, &tag, &tbs) != 0 || tag != 0x30) return -1;
|
||||
|
||||
const uint8_t *cur2 = tbs.p;
|
||||
const uint8_t *limit2 = tbs.p + tbs.len;
|
||||
|
||||
/* version [0] EXPLICIT Version DEFAULT v1 -- present (context-
|
||||
* specific constructed tag 0xA0) for v2/v3 certs, absent for v1.
|
||||
* Peek: if present, consume it; if not, leave cur2 where it is so
|
||||
* the next field (serialNumber) is read correctly either way. */
|
||||
{
|
||||
const uint8_t *peek = cur2;
|
||||
uint8_t ptag;
|
||||
der_span_t pcontent;
|
||||
if (der_next(&peek, limit2, &ptag, &pcontent) == 0 && ptag == 0xA0) {
|
||||
cur2 = peek;
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip serialNumber, signature (AlgorithmIdentifier), issuer,
|
||||
* validity, subject -- exactly 5 fields, none of which this parser
|
||||
* needs the content of, only their length to skip past them. */
|
||||
for (int i = 0; i < 5; i++) {
|
||||
der_span_t skip;
|
||||
if (der_next(&cur2, limit2, &tag, &skip) != 0) return -1;
|
||||
}
|
||||
|
||||
/* subjectPublicKeyInfo ::= SEQUENCE { algorithm, subjectPublicKey } */
|
||||
der_span_t spki;
|
||||
if (der_next(&cur2, limit2, &tag, &spki) != 0 || tag != 0x30) return -1;
|
||||
|
||||
const uint8_t *cur3 = spki.p;
|
||||
const uint8_t *limit3 = spki.p + spki.len;
|
||||
|
||||
/* algorithm ::= AlgorithmIdentifier ::= SEQUENCE { algorithm OID, parameters ANY OPTIONAL } */
|
||||
der_span_t algid;
|
||||
if (der_next(&cur3, limit3, &tag, &algid) != 0 || tag != 0x30) return -1;
|
||||
|
||||
const uint8_t *cur4 = algid.p;
|
||||
const uint8_t *limit4 = algid.p + algid.len;
|
||||
der_span_t oid;
|
||||
if (der_next(&cur4, limit4, &tag, &oid) != 0 || tag != 0x06) return -1;
|
||||
|
||||
/* OID 1.3.101.112 (RFC 8410 Ed25519), DER-encoded: 43 (=40*1+3), 101, 112,
|
||||
* each arc after the first pair fitting in one byte (both < 128). */
|
||||
static const uint8_t ED25519_OID[3] = { 0x2B, 0x65, 0x70 };
|
||||
if (oid.len != sizeof(ED25519_OID) ||
|
||||
memcmp(oid.p, ED25519_OID, sizeof(ED25519_OID)) != 0) {
|
||||
return -1; /* not an Ed25519 key -- refuse, don't guess */
|
||||
}
|
||||
|
||||
/* subjectPublicKey ::= BIT STRING -- 1 unused-bits byte (must be 0
|
||||
* for a byte-aligned 32-byte key) followed by the raw key itself. */
|
||||
der_span_t bitstr;
|
||||
if (der_next(&cur3, limit3, &tag, &bitstr) != 0 || tag != 0x03) return -1;
|
||||
if (bitstr.len != 33 || bitstr.p[0] != 0x00) return -1;
|
||||
|
||||
memcpy(pubkey_out, bitstr.p + 1, 32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int x509_verify_signature(const uint8_t *der, size_t der_len,
|
||||
const uint8_t issuer_pubkey[32]) {
|
||||
if (!der || !issuer_pubkey) return -1;
|
||||
|
||||
const uint8_t *cur = der;
|
||||
const uint8_t *end = der + der_len;
|
||||
uint8_t tag;
|
||||
der_span_t cert;
|
||||
|
||||
/* Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue } */
|
||||
if (der_next(&cur, end, &tag, &cert) != 0 || tag != 0x30) return -1;
|
||||
|
||||
/* tbsCertificate's own encoded bytes -- tag+length+value, the exact
|
||||
* octets Ed25519 signs, not just der_next()'s value-only span. cert.p
|
||||
* is where tbsCertificate's own tag byte starts; der_next() below
|
||||
* advances cur2 past its whole TLV. */
|
||||
const uint8_t *cur2 = cert.p;
|
||||
const uint8_t *limit2 = cert.p + cert.len;
|
||||
const uint8_t *tbs_raw = cur2;
|
||||
der_span_t tbs;
|
||||
if (der_next(&cur2, limit2, &tag, &tbs) != 0 || tag != 0x30) return -1;
|
||||
size_t tbs_raw_len = (size_t)(cur2 - tbs_raw);
|
||||
|
||||
/* signatureAlgorithm ::= AlgorithmIdentifier -- must be Ed25519, same
|
||||
* OID check as SubjectPublicKeyInfo's own algorithm above. */
|
||||
der_span_t sigalg;
|
||||
if (der_next(&cur2, limit2, &tag, &sigalg) != 0 || tag != 0x30) return -1;
|
||||
{
|
||||
const uint8_t *a_cur = sigalg.p;
|
||||
const uint8_t *a_limit = sigalg.p + sigalg.len;
|
||||
uint8_t atag;
|
||||
der_span_t oid;
|
||||
if (der_next(&a_cur, a_limit, &atag, &oid) != 0 || atag != 0x06) return -1;
|
||||
static const uint8_t ED25519_OID[3] = { 0x2B, 0x65, 0x70 };
|
||||
if (oid.len != sizeof(ED25519_OID) ||
|
||||
memcmp(oid.p, ED25519_OID, sizeof(ED25519_OID)) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* signatureValue ::= BIT STRING -- 1 unused-bits byte (must be 0)
|
||||
* followed by the raw 64-byte R||S signature. */
|
||||
der_span_t sigval;
|
||||
if (der_next(&cur2, limit2, &tag, &sigval) != 0 || tag != 0x03) return -1;
|
||||
if (sigval.len != 65 || sigval.p[0] != 0x00) return -1;
|
||||
|
||||
return ed25519_verify(issuer_pubkey, tbs_raw, tbs_raw_len, sigval.p + 1) ? 0 : -1;
|
||||
}
|
||||
|
||||
int x509_extract_serial(const uint8_t *der, size_t der_len,
|
||||
uint8_t *serial_out, size_t serial_out_cap,
|
||||
size_t *serial_len_out) {
|
||||
if (!der || !serial_out || !serial_len_out) return -1;
|
||||
|
||||
const uint8_t *cur = der;
|
||||
const uint8_t *end = der + der_len;
|
||||
uint8_t tag;
|
||||
der_span_t cert;
|
||||
if (der_next(&cur, end, &tag, &cert) != 0 || tag != 0x30) return -1;
|
||||
|
||||
const uint8_t *tbs_cur = cert.p;
|
||||
const uint8_t *tbs_limit = cert.p + cert.len;
|
||||
der_span_t tbs;
|
||||
if (der_next(&tbs_cur, tbs_limit, &tag, &tbs) != 0 || tag != 0x30) return -1;
|
||||
|
||||
const uint8_t *cur2 = tbs.p;
|
||||
const uint8_t *limit2 = tbs.p + tbs.len;
|
||||
|
||||
/* Optional version [0] EXPLICIT -- same peek-and-consume as
|
||||
* x509_extract_ed25519_pubkey() above; serialNumber follows it
|
||||
* directly whether or not it's present. */
|
||||
{
|
||||
const uint8_t *peek = cur2;
|
||||
uint8_t ptag;
|
||||
der_span_t pcontent;
|
||||
if (der_next(&peek, limit2, &ptag, &pcontent) == 0 && ptag == 0xA0) {
|
||||
cur2 = peek;
|
||||
}
|
||||
}
|
||||
|
||||
der_span_t serial;
|
||||
if (der_next(&cur2, limit2, &tag, &serial) != 0 || tag != 0x02) return -1;
|
||||
|
||||
const uint8_t *sp = serial.p;
|
||||
size_t slen = serial.len;
|
||||
/* Strip a single leading 0x00 pad byte -- DER adds one whenever the
|
||||
* value's high bit would otherwise make an unsigned INTEGER read as
|
||||
* negative. */
|
||||
if (slen > 1 && sp[0] == 0x00) { sp++; slen--; }
|
||||
|
||||
if (slen > serial_out_cap) return -1;
|
||||
memcpy(serial_out, sp, slen);
|
||||
*serial_len_out = slen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== DER encoding (x509_build_user_cert) ===== */
|
||||
|
||||
/* Appends one TLV (tag + DER length + content) to *pos, advancing it.
|
||||
* Short-form length only (content_len < 128) -- every field this encoder
|
||||
* ever writes fits comfortably under that, so long-form length encoding
|
||||
* (needed only for der_next()'s decode side, which must handle arbitrary
|
||||
* real-world certs) is not needed here. Returns 0 on success, -1 if
|
||||
* limit would be exceeded. */
|
||||
static int der_put_tlv(uint8_t **pos, uint8_t *limit, uint8_t tag,
|
||||
const uint8_t *content, size_t content_len) {
|
||||
if (content_len >= 128) return -1; /* short-form only, by construction */
|
||||
if ((size_t)(limit - *pos) < 2 + content_len) return -1;
|
||||
*(*pos)++ = tag;
|
||||
*(*pos)++ = (uint8_t)content_len;
|
||||
if (content_len) memcpy(*pos, content, content_len);
|
||||
*pos += content_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ed25519 AlgorithmIdentifier ::= SEQUENCE { OID 1.3.101.112 } (no
|
||||
* parameters) -- same bytes x509_extract_ed25519_pubkey()/
|
||||
* x509_verify_signature() both already recognize. */
|
||||
static int der_put_ed25519_algid(uint8_t **pos, uint8_t *limit) {
|
||||
static const uint8_t ED25519_OID[3] = { 0x2B, 0x65, 0x70 };
|
||||
uint8_t oid_tlv[5];
|
||||
uint8_t *p = oid_tlv;
|
||||
if (der_put_tlv(&p, oid_tlv + sizeof(oid_tlv), 0x06,
|
||||
ED25519_OID, sizeof(ED25519_OID)) != 0) return -1;
|
||||
return der_put_tlv(pos, limit, 0x30, oid_tlv, (size_t)(p - oid_tlv));
|
||||
}
|
||||
|
||||
/* INTEGER-encode a fixed-size unsigned value: prepend a single 0x00 pad
|
||||
* byte if the high bit would otherwise make it read as a negative DER
|
||||
* INTEGER (mirrors x509_extract_serial()'s own strip-a-leading-pad
|
||||
* logic, in reverse). */
|
||||
static int der_put_uint_integer(uint8_t **pos, uint8_t *limit,
|
||||
const uint8_t *val, size_t val_len) {
|
||||
uint8_t buf[24];
|
||||
size_t n = 0;
|
||||
if (val_len + 1 > sizeof(buf)) return -1;
|
||||
if (val[0] & 0x80u) buf[n++] = 0x00;
|
||||
memcpy(buf + n, val, val_len);
|
||||
n += val_len;
|
||||
return der_put_tlv(pos, limit, 0x02, buf, n);
|
||||
}
|
||||
|
||||
size_t x509_build_user_cert(uint8_t *out, size_t out_cap,
|
||||
const uint8_t subject_pubkey[32],
|
||||
const uint8_t serial[16],
|
||||
const uint8_t issuer_seed[32]) {
|
||||
if (!out || !subject_pubkey || !serial || !issuer_seed) return 0;
|
||||
|
||||
uint8_t tbs_content[192];
|
||||
uint8_t *tp = tbs_content;
|
||||
uint8_t *tlim = tbs_content + sizeof(tbs_content);
|
||||
|
||||
/* serialNumber */
|
||||
if (der_put_uint_integer(&tp, tlim, serial, 16) != 0) return 0;
|
||||
/* signature (AlgorithmIdentifier) */
|
||||
if (der_put_ed25519_algid(&tp, tlim) != 0) return 0;
|
||||
/* issuer, validity, subject -- each an empty SEQUENCE; the decode
|
||||
* side only ever skips these three fields by length, never reads
|
||||
* their content (x509_extract_ed25519_pubkey()'s own comment). */
|
||||
if (der_put_tlv(&tp, tlim, 0x30, (const uint8_t *)0, 0) != 0) return 0;
|
||||
if (der_put_tlv(&tp, tlim, 0x30, (const uint8_t *)0, 0) != 0) return 0;
|
||||
if (der_put_tlv(&tp, tlim, 0x30, (const uint8_t *)0, 0) != 0) return 0;
|
||||
/* subjectPublicKeyInfo ::= SEQUENCE { algorithm, subjectPublicKey } */
|
||||
{
|
||||
uint8_t spki[48];
|
||||
uint8_t *sp = spki;
|
||||
uint8_t *slim = spki + sizeof(spki);
|
||||
if (der_put_ed25519_algid(&sp, slim) != 0) return 0;
|
||||
uint8_t bitstr[33];
|
||||
bitstr[0] = 0x00; /* unused-bits */
|
||||
memcpy(bitstr + 1, subject_pubkey, 32);
|
||||
if (der_put_tlv(&sp, slim, 0x03, bitstr, sizeof(bitstr)) != 0) return 0;
|
||||
if (der_put_tlv(&tp, tlim, 0x30, spki, (size_t)(sp - spki)) != 0) return 0;
|
||||
}
|
||||
|
||||
size_t tbs_len = (size_t)(tp - tbs_content);
|
||||
|
||||
/* tbsCertificate's own full TLV bytes (tag+length+content) -- what
|
||||
* Ed25519 actually signs, matching x509_verify_signature()'s own
|
||||
* tbs_raw framing exactly. tbs_len < 128 is a safe assumption for
|
||||
* every field set above, but tbsCertificate as a whole (~150+ bytes)
|
||||
* needs long-form length -- built by hand here rather than reusing
|
||||
* der_put_tlv()'s short-form-only helper. */
|
||||
uint8_t tbs_full[4 + sizeof(tbs_content)];
|
||||
size_t tbs_full_len;
|
||||
if (tbs_len < 128) {
|
||||
tbs_full[0] = 0x30;
|
||||
tbs_full[1] = (uint8_t)tbs_len;
|
||||
memcpy(tbs_full + 2, tbs_content, tbs_len);
|
||||
tbs_full_len = 2 + tbs_len;
|
||||
} else {
|
||||
tbs_full[0] = 0x30;
|
||||
tbs_full[1] = 0x81; /* long-form, 1 length byte follows */
|
||||
tbs_full[2] = (uint8_t)tbs_len;
|
||||
memcpy(tbs_full + 3, tbs_content, tbs_len);
|
||||
tbs_full_len = 3 + tbs_len;
|
||||
}
|
||||
|
||||
uint8_t sig[64];
|
||||
ed25519_sign(issuer_seed, tbs_full, tbs_full_len, sig);
|
||||
|
||||
uint8_t cert_content[4 + sizeof(tbs_full) + 8 + 68];
|
||||
uint8_t *cp = cert_content;
|
||||
uint8_t *clim = cert_content + sizeof(cert_content);
|
||||
|
||||
if ((size_t)(clim - cp) < tbs_full_len) return 0;
|
||||
memcpy(cp, tbs_full, tbs_full_len);
|
||||
cp += tbs_full_len;
|
||||
|
||||
if (der_put_ed25519_algid(&cp, clim) != 0) return 0;
|
||||
|
||||
{
|
||||
uint8_t sigbitstr[65];
|
||||
sigbitstr[0] = 0x00;
|
||||
memcpy(sigbitstr + 1, sig, 64);
|
||||
/* BIT STRING content is 65 bytes >= 128-threshold's neighbor --
|
||||
* still short-form (65 < 128), der_put_tlv() handles it directly. */
|
||||
if (der_put_tlv(&cp, clim, 0x03, sigbitstr, sizeof(sigbitstr)) != 0) return 0;
|
||||
}
|
||||
|
||||
size_t cert_content_len = (size_t)(cp - cert_content);
|
||||
|
||||
/* Outer Certificate ::= SEQUENCE { ... } -- needs long-form length too. */
|
||||
if (cert_content_len < 128) {
|
||||
if (out_cap < 2 + cert_content_len) return 0;
|
||||
out[0] = 0x30;
|
||||
out[1] = (uint8_t)cert_content_len;
|
||||
memcpy(out + 2, cert_content, cert_content_len);
|
||||
return 2 + cert_content_len;
|
||||
} else if (cert_content_len < 256) {
|
||||
if (out_cap < 3 + cert_content_len) return 0;
|
||||
out[0] = 0x30;
|
||||
out[1] = 0x81;
|
||||
out[2] = (uint8_t)cert_content_len;
|
||||
memcpy(out + 3, cert_content, cert_content_len);
|
||||
return 3 + cert_content_len;
|
||||
}
|
||||
return 0; /* every real field set above stays well under 256 total */
|
||||
}
|
||||
Reference in New Issue
Block a user