Phase 8 B: real Ed25519 keygen/signing, verified against OpenSSL

Extends the previously verify-only ed25519.c with ed25519_keygen() and
ed25519_sign() per RFC 8032 5.1.5/5.1.6, reusing every point-arithmetic
primitive verify already had -- only seed expansion/clamping and
per-message nonce derivation are new. Signing is deterministic; only
keygen ever touches entropy, via a caller-supplied seed (virtio_rng,
Phase A) -- keygen still generates nothing itself.

New scalar_muladd() (scalar25519.c) for signing's S = (k*a + r) mod L,
the one scalar op verify never needed. Schoolbook multiply into a u128
wide accumulator with one final carry pass -- deliberately the same
shape as fe25519.c's existing multiply, which has a documented history
of a real bug from carrying mid-accumulation instead of in one pass.

Verified against an independent implementation, not self-consistency:
a throwaway host harness against Python's cryptography library (OpenSSL-
backed) across 6 trials (5 random seed/message pairs + the empty-message
case) produced byte-for-byte identical pubkeys and signatures every
time. Clean compile on all three architectures and a full 3-arch QEMU
acceptance boot, conservation intact, no panics or guest errors.

Nothing calls the new functions from the live kernel path yet -- that's
Phase C (the MINT word itself), still open, documented in FABRIC-3.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
This commit is contained in:
Robert Allan James
2026-08-26 14:46:27 -04:00
co-authored by Claude Sonnet 5
parent 309e792f07
commit 53e6c5709f
11 changed files with 27371 additions and 9 deletions
+92
View File
@@ -188,6 +188,98 @@ static void point_compress(uint8_t out[32], const ed_point_t *p)
}
}
/* 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])
{