Releases: sgeos/keleusma
Keleusma V0.2.0
[0.2.0] - 2026-05-21
V0.2.0 is the first publicly released line. V0.1.x circulated as a pre-release. The headline additions are cryptographic module signing, the V0.2.0 ISA reset, information-flow labels including negative labels, calibrated WCET cost models from the new keleusma-bench crate, and a substantial documentation reorganization. The wire format is incompatible with V0.1.x; recompile artefacts. The release also retires closures, f-strings, and the text bundled DSL; programs that used those features need rewrites under host-registered natives. See the breaking-change subsections below for the migration surface; the V0.1.x line had narrow adoption so the discontinuity is acceptable.
Cryptographic module signing (R42)
V0.2.0 adds optional Ed25519 signing of compiled bytecode. The mechanism enforces origin authenticity and tamper resistance for modules delivered to embedded targets; the motivating use case is the mothership-to-daughtership UAV scenario where a mothership compiles per-mission scripts and a daughtership verifies them over a bandwidth-constrained link.
- Surface syntax. A new
signedmodifier on the entry function declaration (signed fn main,signed yield main,signed loop main) emitsFLAG_REQUIRES_SIGNATURE = 0x02in the module header flags. The modifier is admissible only on the entry function; asignedmodifier on a helper rejects at compile time with a diagnostic naming the offending declaration. - Wire format. The framing header extends through the existing
header_length: u16field. Unsigned modules retain the 64-byte base; signed modules grow to64 + 8 (signature metadata) + signature_length + padding-to-8bytes. Bytes 64..72 hold the signature metadata (scheme_idbyte, reserved byte,signature_length: u16, reserved u32); the signature payload follows. Ed25519 (scheme_id = 1) is the only V0.2.0 scheme; the byte is the substrate for future migrations to ECDSA / ML-DSA / LMS without an ABI break. - Message convention. The signature covers the full framed buffer with the signature payload bytes and the CRC trailer bytes zeroed. Both signer and verifier reconstruct that view.
- Cargo gate. A new optional
signaturesfeature, off by default, bringsed25519-dalek 2underno_std + alloc + zeroize. Builds without the feature accept unsigned bytecode normally and reject signed bytecode withLoadError::SignaturesUnsupported. Thesignedsurface keyword still parses without the feature so source files remain portable across configurations. - Runtime API. New
Vm::load_signed_bytes(bytes, arena, &keys)for the initial signed load. NewVm::replace_module_from_bytes(bytes, initial_data)for signed hot-swap that consults the VM's trust matrix. NewVm::register_verifying_key,Vm::clear_verifying_keys,Vm::verifying_keys_lento manage the matrix.Vm::newrejects modules carryingFLAG_REQUIRES_SIGNATUREdirectly because the signature info is lost when the Module is decoded; callers use the byte-aware entry points.Vm::new_uncheckedskips the check consistent with its existing trust-skip semantics. - Wire-format API. New
wire_format::module_to_signed_wire_bytes(module, signing_key),wire_format::verify_module_signature(bytes, &keys),wire_format::parse_signature_metadata(bytes, header_length),wire_format::header_requires_signature(bytes). - CLI.
keleusma compile script.kel --signing-key seed.bin -o out.binsigns (requiressignedon the entry function).keleusma run out.bin --verifying-key key.pub(repeatable) populates the trust matrix.keleusma keygen --seed seed.bin --public pub.bingenerates a fresh Ed25519 keypair; the seed file is written with0o600permissions on Unix; existing files are not overwritten. Key file format is raw 32-byte Ed25519 seed and public key respectively. - Hardware verification.
examples/rtosgains akeleusma-signaturesfeature passthrough and a boot-timesetup::run_signed_self_testpath. The N6 binary built with the feature on flashes, verifies an embedded signed fixture at boot viaverify_module_signature, logssigned self-test: verify_module_signature succeeded, and enters the scheduler loop. Without the feature, the firmware behaviour is unchanged.
Documentation lives in R42 (docs/decisions/RESOLVED.md), the wire-format spec (docs/spec/WIRE_FORMAT.md), and the migration matrix for future schemes (secret/SIGNATURE_SCHEME_MIGRATION.md, internal).
Wire-format reset
V0.2.0 publishes a new bytecode wire format and the framing-header version field resets to 1. The format is the result of an ISA audit covering opcode consolidation, opcode addition, encoding regularization, and a section-partitioned body layout. See docs/spec/INSTRUCTION_SET.md for the full opcode listing and docs/architecture/EXECUTION_MODEL.md for the wire format specification.
V0.1.x runtimes cannot read V0.2.0 bytecode. V0.2.0 runtimes cannot read V0.1.x bytecode. The version-field reset signals the discontinuity to hosts; existing artefacts must be recompiled against the V0.2.0 toolchain.
The reset is acceptable because the V0.1.x line has narrow adoption. Future wire-format changes will continue to bump the version field rather than reset it.
ISA changes (V0.1.x → V0.2.0)
- Consolidations.
PushTrue,PushFalse,PushUnit,PushNone,WrapSome, andPopare removed.PushImmediate(u8)replaces the four constant-pushing variants and additionally encodes smallIntliterals (Int(0)throughInt(15)) inline.PopN(u8)replacesPop; single-slot pops emitPopN(1).Op::Add,Op::Sub,Op::Mul, andOp::Negremain in the instruction set but no longer acceptValue::Intoperands; the compiler routesIntarithmetic throughCheckedAdd/CheckedSub/CheckedMul/CheckedNegfollowed byPopN(2)to discard the unusedhighandflagoutputs. The unchecked opcodes retain theirByte,Fixed, andFloatarms. - Drops.
CallIndirect,PushFunc,MakeClosure,MakeRecursiveClosureare removed along with the correspondingValue::Funcruntime variant. Closure-shaped surface expressions and first-class function values are rejected at the type-checker stage with a diagnostic that names the construct. The closure-hoisting compiler pass is retired. - Splits.
CallNativeis removed; every native call compiles to eitherCallVerifiedNativeorCallExternalNative. The two are distinguished by the source-levelusedeclaration:use module::nameproducesCallVerifiedNative;use external module::nameproducesCallExternalNative. The host's registration ABI mirrors the distinction throughVm::register_verified_native(name, fn, wcet, wcmu_bytes)andVm::register_external_native(name, fn, max_invocations_per_iteration); the newVm::verify_native_classificationswalks every call site and rejects a classification mismatch asVmError::VerifyErrorat the entry ofVm::call_function(cached after the first successful walk).Vm::register_nativeandVm::register_fncontinue to ascribe the verified classification, preserving backward compatibility for hosts that do not need external semantics. External natives' per-call WCMU contribution is explicitly zeroed at the verifier handoff; chunk-level integration ofmax_invocations_per_iterationis forward-looking. - Additions. Five bitwise opcodes:
BitAnd,BitOr,BitXor,Shl,Shr. Enables script-level bit manipulation and is a prerequisite for the deferred B19 Multiword work. - Operand narrowing. Control-flow opcodes (
If,Else,Loop,EndLoop,Break,BreakIf) carryu16jump targets instead ofu32. Chunks are capped at 65,535 ops as a hardCompileError; the compiler emits aCompileWarningat 80% of the cap (52,428 ops) prompting decomposition into helpers. The newcompile_with_warningsentry point returns(Module, Vec<CompileWarning>);compileandcompile_with_targetdiscard the warnings for callers that do not need them.
Total opcode count at the close of Phase 5 is 69. The audit's aspirational target of 65 anticipated dropping Add / Sub / Mul / Neg (Consolidation B) and CallIndirect / PushFunc / MakeClosure / MakeRecursiveClosure (Phase 4); Phase 5 additionally retired CallNative in favor of the verified-versus-external split. Consolidation B narrowed Add / Sub / Mul / Neg to Byte / Fixed / Float operand types rather than removing them, because the runtime still needs entry points for those non-Int wrapping or IEEE 754 arithmetic forms. The remaining gap of four opcodes against the aspirational target is the cost of preserving direct script-level support for Byte, Fixed, and Float arithmetic alongside the checked-Int family.
Wire format changes
- Fixed-size opcode records. Each opcode is a 4-byte record: 7-bit
opcode_idplus a 1-bit parity in byte zero, 3 bytes inline operand or operand pool index in bytes one through three. Inline operand fields cover 65 of 69 V0.2.0 opcodes; the remaining 4 reference an operand pool. - Separate operand pool. Compound operands (
(u16, u16)and(u16, u16, u8)) live in 8-byte aligned pool entries with a type tag and parity byte. - Section-partitioned body. The framing header carries offsets and lengths for the opcode stream, operand pool, and auxiliary body. Each section is independently relocatable.
- Framing header grows from 32 bytes to 64 bytes to carry the new section offsets and lengths.
The rkyv-archived encoding survives only for the auxiliary body. V0.2.0 Phase 7a publishes the specification and the wire-format types. Phase 7b ships wire_format::module_to_wire_bytes and module_from_wire_bytes as a parallel route. Phase 7c cuts the default Module::to_bytes / Module::from_bytes / Module::access_bytes over to the wire format: `to_b...
keleusma 0.1.1 — MSRV correction
Patch release correcting the MSRV claim in keleusma 0.1.0.
What changed
The published keleusma 0.1.0 declared rust-version = "1.87" in its Cargo.toml, but the source uses let-chains (if let X = a && let Y = b), a syntax stabilised in Rust 1.88. CI surfaced the mismatch immediately after publish: any user on Rust 1.87 attempting to install keleusma 0.1.0 would hit error[E0658]: 'let' expressions in this position are unstable during compile.
This release bumps the declared MSRV to 1.88 across keleusma, keleusma-cli, and keleusma-bench to match the actual feature use. keleusma-arena retains MSRV 1.85 (its source does not use let-chains).
The CI workflow's msrv-keleusma job is bumped from 1.87 to 1.88 so any future MSRV drift is caught at PR time rather than at publish time.
No source changes; runtime behaviour is identical to 0.1.0.
Action for users
| Toolchain | What to do |
|---|---|
| Rust 1.87 | Upgrade to 1.88+, or pin keleusma = "=0.1.1" (which works at 1.88+). |
| Rust 1.88 or newer | cargo update -p keleusma picks up 0.1.1 automatically. |
keleusma 0.1.0 has been yanked from crates.io. Existing Cargo.lock files referencing 0.1.0 continue to resolve, but new cargo add keleusma invocations select 0.1.1.
Workspace state
| Crate | Version | Status |
|---|---|---|
keleusma |
0.1.1 (this release) | published |
keleusma-arena |
0.2.0 | unchanged |
keleusma-macros |
0.1.0 | unchanged |
License
BSD Zero Clause License (0BSD).