Milestone 6 (ACL/PKI): Ed25519 verify + SHA-512, built from scratch

New freestanding, verify-only Ed25519 (RFC 8032) implementation:
include/starkernel/{sha512,fe25519,scalar25519,ed25519}.h +
src/starkernel/crypto/{sha512,fe25519,scalar25519,ed25519}.c, wired into
Makefile.starkernel. Kernel never signs or generates keys -- only
ed25519_verify() is needed; signing happens in the host-side mkcapsule
build tool via libsodium/OpenSSL.

Confirmed __int128 multiply/add/shift-by-constant compile with zero
undefined symbols on all three target toolchains (only division needs
libgcc's __udivti3, per timer.c's existing documented finding -- that
file's comment updated to narrow the claim, since it had been read as
"avoid __int128 entirely"). This enabled the standard 5-limb radix-2^51
field arithmetic representation.

An abandoned first attempt (10-limb radix-2^26, avoiding __int128 out of
premature caution) hit two real bugs, both invisible on inspection and
found only by property-based testing against Python's own bignum
arithmetic: a non-uniform-radix limb misalignment in multiplication, and
a double-counted carry. Verification chain: SHA-512 against known +
boundary vectors (7/7); field arithmetic property-tested 25,045 cases;
scalar-mod-L arithmetic 300 cases (L confirmed prime via Miller-Rabin
first); full verify() end-to-end against 110 real signatures from
Python's cryptography library, including tampered inputs and the RFC
8032 S>=L malleability attack -- all correctly accepted/rejected.

Compiles clean (zero warnings) and links on all three architectures,
confirmed via the mandatory three-arch QEMU boot. The code is linked but
not yet called from anywhere -- wiring into capsule_birth.c needs a
from-scratch X.509/DER parser first (Captain Bob chose real X.509 over a
raw-blob cert format this session), which is the next open item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
This commit is contained in:
Robert Allan James
2026-08-22 12:41:26 -04:00
co-authored by Claude Sonnet 5
parent 2b7743027c
commit 2e7e957680
17 changed files with 28046 additions and 5 deletions
+78 -2
View File
@@ -3461,8 +3461,12 @@ sized around, not the size of every test image.
key needed at all.** Trust is established once, at build time, by whoever holds the real
root CA and produces the build — the embedded intermediate is already signed before it
ever reaches the kernel, so there's no runtime chicken-and-egg to solve.
- [ ] Implement per-capsule signature verification (Ed25519) at capsule-load time, checked
against the embedded (already-trusted) intermediate cert directly
- [x] Implement per-capsule signature verification (Ed25519) at capsule-load time, checked
against the embedded (already-trusted) intermediate cert directly — **the Ed25519
cryptographic primitive itself is done, 2026-08-22, see writeup below.** Wiring it into
`capsule_birth.c`'s actual load path, and the X.509/DER parsing needed to read the
embedded cert (Captain Bob chose real X.509 over a raw-blob format, 2026-08-22 — see
writeup), are both still open.
- [ ] Add a signing step to the `mkcapsule` build tool (or a separate signing tool) that
produces a signature alongside each capsule's existing xxHash64
- [ ] Extend `MANIFEST_AUTO.md`'s generation to add a signature-status column, matching the
@@ -3470,6 +3474,78 @@ sized around, not the size of every test image.
- [ ] Implement magic-number-based content-type detection (Section U item 14) — a shared
primitive, per Section V, also usable for Milestone 4's foreign-drive check
**Ed25519 verification primitive, done 2026-08-22.** Kernel needs only `ed25519_verify()` --
this kernel never signs or generates keys (no entropy source to do so safely anyway); signing
happens in the host-side build tool, which can link libsodium/OpenSSL since it's a normal
Linux binary. That halved the scope: no scalar clamping, no key derivation, no constant-time
discipline (verify only ever touches public data -- pubkey, message, signature -- no
secret-dependent branch to leak).
New files: `include/starkernel/{sha512,fe25519,scalar25519,ed25519}.h` +
`src/starkernel/crypto/{sha512,fe25519,scalar25519,ed25519}.c`, wired into
`Makefile.starkernel`'s source wildcards (both `LOADER_SRCS_BASE` and `KERNEL_SRCS_BASE`).
Confirmed present in this tree: none of SHA-512, Ed25519, or any big-integer field arithmetic
existed anywhere before this (`tools/mkcapsule.c`'s "anchor for future Ed25519 fingerprints"
comment was the only prior trace).
**`__int128` is usable here after all -- for multiply/add/shift, just not divide.**
`arch/amd64/timer.c`'s own history (see that file, and this milestone's memory note) documents
a prior `__int128` attempt failing because 128-bit *division* needs libgcc's `__udivti3`,
undefined in this `-ffreestanding -nostdlib` build -- which had been read as "avoid `__int128`
entirely." Checked directly this session, against the exact `Makefile.starkernel` flags, on all
three target toolchains (`gcc`, `aarch64-linux-gnu-gcc`, `riscv64-linux-gnu-gcc`): `__int128`
multiply-accumulate and shift-by-constant compile with zero undefined symbols. `timer.c`'s
comment updated to narrow the claim to division specifically. This let the field arithmetic use
the standard, widely-reviewed **5-limb, radix-2^51** Ed25519 representation (51*5=255 bits
exactly -- the reduction constant is the clean `19`, matching `2^255 mod p`) instead of a
from-scratch 10-limb/26-bit scheme designed to avoid `__int128` out of premature caution.
**That from-scratch scheme was tried first and abandoned after two real bugs, not from
`__int128` unavailability.** Documented here because the failure mode is instructive: (1) a
fundamentally flawed limb-alignment assumption -- for a non-uniform (26/25-bit alternating)
radix, `shift[i]+shift[j] != shift[i+j]` in general, so naive schoolbook multiplication silently
misaligned terms; found by property-based testing against Python's own bignum arithmetic, not
by inspection -- inspection had already declared the code correct. (2) After switching to a
uniform 26-bit radix to fix that, a double-counted carry (`wide[i+1] += c` both explicitly at
the end of one loop iteration and implicitly via `wide[i] += c` at the top of the next, using
the same stale `c`) silently doubled every multiplication result. Both bugs produced plausible-
looking, internally-consistent-seeming output; neither was visible from reading the code, only
from testing against an independent reference. This is the second time this session a
"the math looks right" belief was wrong until checked against ground truth (the first was a
hand-transcribed SHA-512 test vector, off by one hex digit) -- **treat any hand-derived
numeric/algorithmic claim in crypto or register-layout code as unverified until checked against
an independent source, not as verified because it was reasoned through carefully.**
**Verification method, in order:** (1) SHA-512 against known vectors (`empty`, `"abc"`) plus
five boundary-case vectors independently generated via `sha512sum` (111/112/113/128/130-byte
inputs, exercising the padding-wraparound path) -- 7/7 pass. (2) Field arithmetic
(`fe25519_{add,sub,mul,sq,invert}`) against Python's own `pow()`/modular arithmetic,
property-based, 25,045 random + edge cases -- ALL PASS (this is what caught both bugs above).
(3) Scalar arithmetic mod `L` (`scalar_reduce512`, `scalar_lt_L`) similarly, 300 cases -- ALL
PASS. `L` itself (`2^252 + 27742317777372353535851937790883648493`) confirmed prime via 20
rounds of Miller-Rabin in Python before use, rather than trusted from memory. (4) The full
`ed25519_verify()` pipeline end-to-end against 110 real signatures generated by Python's
`cryptography` library (itself backed by a well-audited OpenSSL) -- valid signatures across
message lengths 0..1000 bytes, tampered messages, tampered signatures, wrong public keys, and
the RFC 8032 `S >= L` malleability attack, all correctly accepted/rejected. Curve constants (`d`,
base point `Bx`/`By`, `sqrt(-1)`, the `(p+3)/8` exponent) were all computed directly via Python
arbitrary-precision arithmetic and pasted into the C source as byte arrays, not hand-derived or
recalled -- the base point's encoding (`0x58, 0x66, 0x66, ...`) matches the widely-published
Ed25519 base point as a corroborating check.
Compiles clean (`-Wall -Werror -Wextra`, zero warnings) and links on all three architectures;
confirmed via the mandatory three-arch QEMU boot (`logs/20260822-123758/amd64/`,
`logs/20260822-123842/aarch64/`, `logs/20260822-123701/riscv64/`) -- the code is not called from
anywhere yet (dead but linked), so this confirms compile/link correctness across toolchains,
not runtime behavior in the kernel specifically; the host-side test harnesses above are what
established runtime correctness.
Still open, per Captain Bob's X.509 decision this session (2026-08-22): a from-scratch DER/
ASN.1 parser and X.509 structure walker (scoped to exactly what's needed -- Ed25519
`SubjectPublicKeyInfo`/signature per RFC 8410, Issuer/Subject/Validity, chain matching -- not a
general RFC 5280 parser), before the embedded intermediate cert can actually be read and
`capsule_birth.c` can be wired to verify against it.
### Milestone 7 — Contributor capsules / trust tiers (Section U items 15-18, Section V area F)
Explicitly sequenced after Milestone 6 closes.
+2
View File
@@ -392,6 +392,7 @@ LOADER_SRCS_BASE := \
$(wildcard $(KERNEL_SRC)/memory/*.c) \
$(wildcard $(KERNEL_SRC)/math/*.c) \
$(wildcard $(KERNEL_SRC)/hash/*.c) \
$(wildcard $(KERNEL_SRC)/crypto/*.c) \
$(wildcard $(KERNEL_SRC)/capsule/*.c) \
$(wildcard $(KERNEL_SRC)/pci/*.c) \
$(wildcard $(KERNEL_SRC)/virtio/*.c) \
@@ -441,6 +442,7 @@ KERNEL_SRCS_BASE := \
$(wildcard $(KERNEL_SRC)/memory/*.c) \
$(wildcard $(KERNEL_SRC)/math/*.c) \
$(wildcard $(KERNEL_SRC)/hash/*.c) \
$(wildcard $(KERNEL_SRC)/crypto/*.c) \
$(wildcard $(KERNEL_SRC)/vm_arbiter/*.c) \
$(wildcard $(KERNEL_SRC)/capsule/*.c) \
$(wildcard $(KERNEL_SRC)/pci/*.c) \
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-22T15:42:12Z -->
<!-- Generated by mkcapsule --manifest 2026-08-22T16:38:40Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
+23
View File
@@ -0,0 +1,23 @@
/* ed25519.h -- EdDSA signature VERIFICATION only (RFC 8032), freestanding
* C99. No signing, no key generation, no RNG -- this kernel never signs;
* signing happens in the host-side build tool, which can link
* libsodium/OpenSSL because it's a normal Linux binary. That halves the
* implementation surface here: no scalar clamping, no key derivation, no
* constant-time discipline (verify operates only on public data --
* public key, message, signature -- there's no secret-dependent branch
* to leak).
*/
#ifndef ED25519_H
#define ED25519_H
#include <stdint.h>
#include <stddef.h>
/* Returns 1 if signature (64 bytes: R || S) is a valid Ed25519 signature
* by pubkey (32 bytes, compressed point) over msg, else 0. Rejects
* malformed inputs (S >= L, an undecodable point) as invalid rather than
* faulting. */
int ed25519_verify(const uint8_t pubkey[32], const uint8_t *msg, size_t msg_len,
const uint8_t sig[64]);
#endif /* ED25519_H */
+60
View File
@@ -0,0 +1,60 @@
/* fe25519.h -- arithmetic mod p = 2^255-19, for Ed25519.
*
* Five-limb representation, uniform radix 2^51 (value =
* sum(limb[i] * 2^(51*i)), limb i in roughly [0, 2^51)) -- the standard
* Ed25519 reference layout (matches the widely-reviewed "amd64-51"-style
* implementations), not something invented for this codebase. 51*5=255
* exactly, so unlike a mismatched limb-count/width choice, the reduction
* constant is the clean 2^255 mod p = 19 with no extra scaling.
*
* Uses __int128 for multiply-accumulate (product of two ~51-bit limbs is
* up to ~102 bits, summed across up to 5 terms per bucket -- needs a
* wide type). Confirmed safe in this kernel's freestanding -nostdlib
* build by direct toolchain testing (gcc/aarch64-linux-gnu-gcc/
* riscv64-linux-gnu-gcc, matching Makefile.starkernel's exact flags):
* __int128 multiply, add, and shift-by-constant all compile with zero
* undefined symbols on all three target architectures. This is DIFFERENT
* from __int128 DIVISION, which src/starkernel/arch/amd64/timer.c
* documents as broken (needs libgcc's __udivti3, undefined in this
* -nostdlib build) -- this file never divides __int128 values, so that
* restriction doesn't apply here. An earlier draft of this file avoided
* __int128 entirely (10 limbs, radix 2^26, int64_t only) out of
* over-caution before this was checked directly; abandoned after running
* into real bugs from that scheme's own complexity, not from __int128
* unavailability -- __int128 was never actually the constraint once
* verified.
*/
#ifndef FE25519_H
#define FE25519_H
#include <stdint.h>
/* int64_t, not uint64_t: fe25519_sub produces negative intermediate
* limbs (a[i] - b[i] can be < 0 for a specific limb even when the total
* value a-b, mod p, is what's wanted), and fe25519_carry() relies on
* arithmetic right shift to propagate negative "borrows" the same way
* it propagates positive carries -- proven correct by the property-based
* host test, not just assumed. */
typedef struct { int64_t v[5]; } fe25519;
void fe25519_0(fe25519 *r);
void fe25519_1(fe25519 *r);
void fe25519_copy(fe25519 *r, const fe25519 *a);
void fe25519_add(fe25519 *r, const fe25519 *a, const fe25519 *b);
void fe25519_sub(fe25519 *r, const fe25519 *a, const fe25519 *b);
void fe25519_neg(fe25519 *r, const fe25519 *a);
void fe25519_mul(fe25519 *r, const fe25519 *a, const fe25519 *b);
void fe25519_sq(fe25519 *r, const fe25519 *a);
void fe25519_invert(fe25519 *r, const fe25519 *a);
void fe25519_mul_small(fe25519 *r, const fe25519 *a, uint32_t c);
/* Pack to 32 little-endian bytes (fully reduced mod p) / unpack from same. */
void fe25519_pack(uint8_t out[32], const fe25519 *a);
void fe25519_unpack(fe25519 *r, const uint8_t in[32]);
/* 1 if a == b (as field elements, after full reduction), else 0. */
int fe25519_eq(const fe25519 *a, const fe25519 *b);
/* Parity of the fully-reduced value's low bit (used for point decompression's sign bit). */
int fe25519_parity(const fe25519 *a);
#endif /* FE25519_H */
+29
View File
@@ -0,0 +1,29 @@
/* scalar25519.h -- arithmetic mod L (the Ed25519 base point's order),
* for reducing SHA-512 output to a valid scalar and checking a
* signature's S component for the RFC 8032 malleability requirement
* (S < L, not just S < 2^256).
*
* Deliberately NOT the intricate hand-tuned "sc_reduce" reduction most
* reference implementations use (a bespoke Barrett-style reduction with
* constants specific to L, notoriously easy to transcribe wrong) --
* this is a plain binary long-division reduction, one bit at a time.
* O(512) steps per reduction; this is a verify-only, non-hot-path
* library (one reduction per signature check), so the simpler,
* more obviously-correct approach is the right tradeoff here.
*/
#ifndef SCALAR25519_H
#define SCALAR25519_H
#include <stdint.h>
/* 32-byte little-endian scalars, reduced mod L where noted. */
/* Reduce a 64-byte little-endian value (e.g. raw SHA-512 output) mod L,
* producing a 32-byte little-endian result < L. */
void scalar_reduce512(uint8_t out[32], const uint8_t in[64]);
/* 1 if the 32-byte little-endian scalar is < L (a well-formed,
* non-malleable signature component per RFC 8032), else 0. */
int scalar_lt_L(const uint8_t s[32]);
#endif /* SCALAR25519_H */
+30
View File
@@ -0,0 +1,30 @@
/* sha512.h -- freestanding SHA-512 (FIPS 180-4 / RFC 6234), C99, no libc
* beyond memcpy/memset (both available in the kernel via
* src/starkernel/vm/host/shim.c). No __int128 used -- 64-bit words only,
* portable to amd64/aarch64/riscv64 without libgcc helpers.
*/
#ifndef SHA512_H
#define SHA512_H
#include <stdint.h>
#include <stddef.h>
typedef struct {
uint64_t state[8];
uint64_t bitlen; /* total message length in bits, low 64 bits
* (SHA-512 defines a 128-bit length field; a
* single uint64_t of bit-length is enough for
* any message this kernel will ever hash --
* capsules and certs, not exabyte streams) */
uint8_t buf[128];
size_t buf_len;
} sha512_ctx_t;
void sha512_init(sha512_ctx_t *ctx);
void sha512_update(sha512_ctx_t *ctx, const uint8_t *data, size_t len);
void sha512_final(sha512_ctx_t *ctx, uint8_t out[64]);
/* Convenience one-shot. */
void sha512(const uint8_t *data, size_t len, uint8_t out[64]);
#endif /* SHA512_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -148,8 +148,15 @@ static uint64_t vm_ns_base = 0;
* to a double fault and then a triple fault (silently exited by
* `-no-reboot`, which is why this looked like an infinite hang rather than
* a crash at first). A `unsigned __int128` rewrite was tried first but
* needs libgcc's `__udivti3` for the general 128÷64 case, undefined in this
* freestanding, `-nostdlib` build not viable. Fixed instead the way the
* needs libgcc's `__udivti3` for the general 128÷64 DIVISION case,
* undefined in this freestanding, `-nostdlib` build not viable for that
* reason specifically. (Confirmed narrower during the Ed25519 crypto work,
* 2026-08-22: `__int128` multiply/add/shift-by-constant DO compile cleanly
* with zero undefined symbols on all three target toolchains it's only
* division, needing a multi-instruction libgcc routine the compiler won't
* inline, that's unavailable here. Don't read this comment as "avoid
* __int128 entirely" — src/starkernel/crypto/fe25519.c uses it safely for
* exactly that reason.) Fixed instead the way the
* Linux kernel's own `mul_u64_u64_div_u64` does it: declare `rdx` as a pure
* *clobber*, not an output. A clobber tells GCC the register is used
* internally by the whole asm block and must never be allocated to any
+235
View File
@@ -0,0 +1,235 @@
/* 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-2.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;
}
}
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;
}
+249
View File
@@ -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;
}
+74
View File
@@ -0,0 +1,74 @@
/* 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);
}
+137
View File
@@ -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);
}