Skip to content

Releases: sgeos/keleusma

Keleusma V0.2.0

21 May 23:28

Choose a tag to compare

[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 signed modifier on the entry function declaration (signed fn main, signed yield main, signed loop main) emits FLAG_REQUIRES_SIGNATURE = 0x02 in the module header flags. The modifier is admissible only on the entry function; a signed modifier 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: u16 field. Unsigned modules retain the 64-byte base; signed modules grow to 64 + 8 (signature metadata) + signature_length + padding-to-8 bytes. Bytes 64..72 hold the signature metadata (scheme_id byte, 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 signatures feature, off by default, brings ed25519-dalek 2 under no_std + alloc + zeroize. Builds without the feature accept unsigned bytecode normally and reject signed bytecode with LoadError::SignaturesUnsupported. The signed surface 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. New Vm::replace_module_from_bytes(bytes, initial_data) for signed hot-swap that consults the VM's trust matrix. New Vm::register_verifying_key, Vm::clear_verifying_keys, Vm::verifying_keys_len to manage the matrix. Vm::new rejects modules carrying FLAG_REQUIRES_SIGNATURE directly because the signature info is lost when the Module is decoded; callers use the byte-aware entry points. Vm::new_unchecked skips 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.bin signs (requires signed on the entry function). keleusma run out.bin --verifying-key key.pub (repeatable) populates the trust matrix. keleusma keygen --seed seed.bin --public pub.bin generates a fresh Ed25519 keypair; the seed file is written with 0o600 permissions on Unix; existing files are not overwritten. Key file format is raw 32-byte Ed25519 seed and public key respectively.
  • Hardware verification. examples/rtos gains a keleusma-signatures feature passthrough and a boot-time setup::run_signed_self_test path. The N6 binary built with the feature on flashes, verifies an embedded signed fixture at boot via verify_module_signature, logs signed 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, and Pop are removed. PushImmediate(u8) replaces the four constant-pushing variants and additionally encodes small Int literals (Int(0) through Int(15)) inline. PopN(u8) replaces Pop; single-slot pops emit PopN(1). Op::Add, Op::Sub, Op::Mul, and Op::Neg remain in the instruction set but no longer accept Value::Int operands; the compiler routes Int arithmetic through CheckedAdd / CheckedSub / CheckedMul / CheckedNeg followed by PopN(2) to discard the unused high and flag outputs. The unchecked opcodes retain their Byte, Fixed, and Float arms.
  • Drops. CallIndirect, PushFunc, MakeClosure, MakeRecursiveClosure are removed along with the corresponding Value::Func runtime 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. CallNative is removed; every native call compiles to either CallVerifiedNative or CallExternalNative. The two are distinguished by the source-level use declaration: use module::name produces CallVerifiedNative; use external module::name produces CallExternalNative. The host's registration ABI mirrors the distinction through Vm::register_verified_native(name, fn, wcet, wcmu_bytes) and Vm::register_external_native(name, fn, max_invocations_per_iteration); the new Vm::verify_native_classifications walks every call site and rejects a classification mismatch as VmError::VerifyError at the entry of Vm::call_function (cached after the first successful walk). Vm::register_native and Vm::register_fn continue 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 of max_invocations_per_iteration is 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) carry u16 jump targets instead of u32. Chunks are capped at 65,535 ops as a hard CompileError; the compiler emits a CompileWarning at 80% of the cap (52,428 ops) prompting decomposition into helpers. The new compile_with_warnings entry point returns (Module, Vec<CompileWarning>); compile and compile_with_target discard 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_id plus 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...

Read more

keleusma 0.1.1 — MSRV correction

10 May 14:18

Choose a tag to compare

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).