Skip to content

update: the image signature on toyos-crypto's Ed25519; ed25519-dalek is an oracle - #1019

Merged
Japabu merged 3 commits into
mainfrom
wt/toyos-crypto-adopt-ed25519
Oct 22, 2026
Merged

Japabu merged 3 commits into
mainfrom
wt/toyos-crypto-adopt-ed25519

Conversation

@Japabu

@Japabu Japabu commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The owner asked (2026-10-22) for the tree's own crypto wherever possible. The image signature still verified with ed25519-dalek's verify_strict in the loader, /system/bin/update and the host, and signed with it on the host. In the soft-float loader, ed25519-dalek also needed sha2 built force-soft, and the UEFI targets set curve25519_dalek_backend for it.

Change

  • lib/update verifies with toyos-crypto's strict RFC 8032 Ed25519 everywhere. It signs with it on the host, and derives a seed's public key with it there too.
    • Signing and public-key derivation are behind sign, as signing already was, because deriving a public key handles the seed. build/ssh is host-only and enables the feature.
  • ed25519-dalek 2.2.0 is now only a dev-dependency (an oracle). The loader's sha2 dependency and the UEFI targets' curve25519_dalek_backend cfg are gone. The loader's graph has no dalek and no sha2.
  • Verdicts. They match verify_strict on every signature anyone can make and on every canonical key.
    • One difference, for the owner: the crate refuses a public key whose y is p or more, as RFC 8032 §5.1.3 requires. verify_strict accepts such a key when the point is not small-order.
    • Every key the tree verifies against is one its build minted, and those are canonical. So no verdict on a key the tree holds changes.
    • lib/update/tests/ed25519_oracles.rs names the encodings where the two differ, and asserts that the difference is real.
  • Oracles.
    • ed25519_oracles.rs checks 2,000 seeded seeds and headers against RustCrypto and ring:
      • the public key and the signature bytes are identical to RustCrypto's (and ring's public key);
      • the verdicts agree on changed signatures, changed headers and S + L.
    • The twelve cases of "Taming the many EdDSAs" (Chalkias et al., 2020) are now committed, pinned in NOTICE and COMMITTED_FILES, and run in the crate's differential.
      • Every verdict is verify_strict's.
      • Ring accepts cases 0, 1, 2 and 11, which strict verification refuses. Its verdicts are pinned in the test.
      • Awaiting the owner: stage 6's exit, kept as the owner wrote it, asks for agreement with both verify_strict and ring. No verifier can meet both, and the issue records this as the owner's to decide.
  • Loader timing. The loader logs the signature check's duration in counter ticks, the measurement stage 6 asks of each bench.

Checks

  • cargo run -- --ci host at 69245a2: exit 0, 93 steps green.
  • cargo test -p toyos-update --features sign: all pass, including the RFC 8032 vectors and the OpenSSH-signed sshsig_oracle.
  • Negative control: sig::verify made to accept every signature turns signatures_and_verdicts_match_dalek_and_ring red (exit 101). It was reverted in the same run.
  • Images: cargo run -- --build-only for x86-64 and for AArch64, exit 0.
  • QEMU: a_bent_supervisor_is_refused_and_the_other_slot_boots passes on QEMU's x86-64 and AArch64 machines. The loader verifies the slots' signatures with the crate. The AArch64 guest's loader logs the timing line; the x86-64 guest's kernel shows slot A verified before its hash was refused.
  • The licence gate and sourcegate's committed-file check pass.

Evidence: https://github.com/ToyOSOrg/evidence/tree/7ef01c1c4a717032f79a0fbace333940e88de66a/pr-1019

Bench readings

The loader's verification time, stage 6's last exit, from the probe boots that timed #1016's SHA-256:

  • T14: 253,975 counter ticks at the stated 2.4192 GHz, about 105 µs;
  • AMD bench: 122,724 ticks, on a CPU that states no counter rate.

Evidence for review r1: https://github.com/ToyOSOrg/evidence/tree/b3e027cb3655b4ffb530ebab66cdd0052d72d31c/pr-1019/r1

🤖 Generated with Claude Code

https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q

Japabu added a commit that referenced this pull request Oct 22, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
@Japabu

Japabu commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 1: #1019 at 269e389 (code 69245a2), stacked on #1016

Architecture

The image signature now uses the crate. ed25519-dalek is dev-only (an oracle), and the loader's force-soft sha2 and the UEFI targets' curve25519_dalek_backend cfg are deleted. The Cargo.lock diff confirms it: bootloader drops sha2, and toyos-update gains only the dev ring. Signing and public-key derivation moved behind sign, and every caller enables it: the root manifest's toyos-build, build/root, build/ssh and update-copies. No guest package enables it. Net change: +429/−59. Of that, 201 lines are the speccheck licence text and 134 are tests; production code shrinks.

Strictness, checked against RFC 8032 §5.1.3 and §5.1.7 (lib/crypto/src/ed25519.rs)

  • S: PublicScalar::canonical requires S < L. This addresses malleability: the S + L cases in ed25519_oracles.rs are refused, as both oracles agree.
  • A: Point::decode refuses y ≥ p (step 1), a non-square x² (step 3), and x = 0 with the sign bit set (step 4). PublicKey::from_bytes also refuses the eight canonical small-order encodings. After canonical decoding these are the only small-order encodings, so the table is complete. Mixed-order keys are accepted, as verify_strict does.
  • R: R is never decoded. The verifier compares the canonical encoding of [S]B − [k]A with R's bytes. That comparison refuses any non-canonical, off-curve or negative-zero R exactly as decode-then-compare would. The small-order R encodings are refused explicitly.
  • k: k = SHA-512(R ‖ A ‖ M) mod L over the key's given (canonical) bytes, with the cofactorless equation. §5.1.7 permits the cofactorless check, and it matches verify_strict.

The speccheck verdicts (speccheck-verdicts.txt) and the committed test agree on all twelve cases. The one divergence, y ≥ p keys, is the RFC's rule. keys_match_dalek_but_for_the_encodings_rfc_8032_refuses checks it correctly (y ∈ [p, 2^255) is exactly [0xed..=0xff, 0xff×30, 0x7f], and the x = 0 aside holds because x = 0 forces y = ±1). I agree with following the RFC here. Accepting it remains the owner's call, and the issue says so.

Required

  1. issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md:497-503: an owner-written exit is reworded and then marked "Done". Stage 6's exit, as the owner set it, says the crate "agrees with ed25519-dalek's verify_strict and with ring". The branch rewrites this to "with ring where ring is strict. Done (update: the image signature on toyos-crypto's Ed25519; ed25519-dalek is an oracle #1019)". Ring's leniency on cases 0, 1, 2 and 11 is a real discovery, and pinning ring's verdicts is the right test. But dropping part of an owner exit is the owner's decision. The handoff itself lists it under "Decisions for the owner", and the PR body presents it as settled. Keep the owner's wording, record the discovery, and mark this item as awaiting the owner, as the y ≥ p bullet (lines 505-511) already does.

Suggestions

  • PR body / evidence README: "the run logs the timing line". In qemu-bent-supervisor.log the loader's lines, including the tick count, appear only for the AArch64 guest. The x86-64 guest shows only the kernel's "slot B, because the marked slot A was refused: hash". That still shows slot A's signature verified before its hash refused it, but say which architecture logged the line.
  • lib/update/Cargo.toml:25-26: "with no table the soft-float loader cannot hold" is vague, because the verifier does carry the static B_ODD table. Delete the clause, or say what it rules out (e.g. no runtime-built tables).

Evidence

  • ci-host.rev = 69245a2. Exit 0. 269e389 only edits the issue.
  • The negative control (accept-all) turns signatures_and_verdicts_match_dalek_and_ring red. A control that removes the y < p check would turn the key test red by construction (parted > 0), and the crate's noncanonical_encodings_and_negative_zero_are_refused covers it too, so no further control is needed.
  • The oracles are independent of the crate (RustCrypto and ring), and the RFC settles the one disagreement.
  • Stage 6's bench timing is correctly left pending.

#1019 inherits #1016's open items.

SEND BACK: item 1, a record fix.

Japabu and others added 3 commits October 22, 2026 21:04
…is an oracle

The owner (2026-10-22): use our own crypto where possible. lib/update
verifies with toyos-crypto's strict RFC 8032 Ed25519 in the loader, the
updater and the host, and signs with it on the host. ed25519-dalek and the
loader's `force-soft` sha2 leave the non-dev graph, and with them the UEFI
targets' `curve25519_dalek_backend` cfg.

- Signing and a seed's public key are `sign`'s, as signing already was:
  deriving a public key from a seed handles the secret too. build/ssh,
  host-only, takes the feature.
- The verdicts are verify_strict's on every signature anyone can make and
  every canonical key. The crate refuses a key whose y is p or more, as
  RFC 8032 section 5.1.3 requires, where verify_strict accepts it when the
  point is not small-order. Every key the tree verifies against is one its
  build minted, which is canonical. `lib/update/tests/ed25519_oracles.rs`
  holds 2,000 seeded signatures beside RustCrypto and ring, byte for byte,
  with changed signatures, headers and S + L, and names the encodings where
  the two verifiers part.
- The twelve cases of "Taming the many EdDSAs" (Chalkias et al., 2020)
  join the crate's differential: every verdict is verify_strict's, and
  ring's four lenient ones (cases 0, 1, 2 and 11) are pinned.
- The loader logs the signature check's counter ticks, which stage 6 asks
  of each bench.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
…g the owner

Review r1 of #1019. The exit asks agreement with verify_strict and with
ring; ring accepts four of the twelve speccheck cases verify_strict
refuses, so no verifier meets both. That stays the owner's to decide,
recorded beside the y >= p difference rather than reworded as done. The
loader's verify time on both benches, from the probe boots: 253,975
ticks on the T14 (about 105 us), 122,724 on the AMD bench.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXEk8BBMDT8tXu4AnENB9Q
@Japabu
Japabu force-pushed the wt/toyos-crypto-adopt-ed25519 branch from 269e389 to 9825b99 Compare October 22, 2026 19:29
@Japabu

Japabu commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Response to review r1. The head is 9825b99, rebased onto #1016's e5414b9.

  1. Stage 6's exit: restored to the owner's wording ("agrees with ed25519-dalek's verify_strict and with ring"). Below it, awaiting the owner: ring accepts speccheck cases 0, 1, 2 and 11, which verify_strict refuses, so no verifier meets both. The test pins ring's verdicts. The PR body no longer presents this as settled.

The bench time for the same exit is now recorded: 253,975 counter ticks on the T14 at 2.4192 GHz (about 105 µs), and 122,724 on the AMD bench, which states no counter rate.

Suggestions, both taken:

  • The body and the evidence say the QEMU timing line is the AArch64 guest's.
  • The vague "no table" clause in lib/update/Cargo.toml is gone.

Evidence: https://github.com/ToyOSOrg/evidence/tree/b3e027cb3655b4ffb530ebab66cdd0052d72d31c/pr-1019/r1

Host CI ran at the stack's head (c195cf5): exit 0, 93 steps.

@Japabu

Japabu commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 2: #1019 at 9825b99 (rebased onto #1016's e5414b9)

Round 1's one required item is resolved. Stage 6's exit (issues/…-secure.md:499-508) is back to the owner's wording ("agrees with ed25519-dalek's verify_strict and with ring"). Ring's leniency on cases 0, 1, 2 and 11 is recorded below it as awaiting the owner, beside the y ≥ p item. Both owner questions are open as the orchestrator described, and neither blocks landing.

The range-diff against round 1 shows that the code commits differ only by the rebase.

New since round 1:

  • The loader verify-time item is marked done. The cited ticks match pr-1016/r1/bench/{t14,amd}-loader-verify.txt: 253,975 ticks on the T14 at 2.4192 GHz, about 105 µs, and 122,724 on the AMD bench. They come from the probe image, whose verifier is this PR's.
  • The vague clause in lib/update/Cargo.toml is gone.
  • The QEMU timing line is now attributed to the AArch64 guest.

LAND

Base automatically changed from wt/toyos-crypto-adopt to main October 22, 2026 20:14
@Japabu
Japabu marked this pull request as ready for review October 22, 2026 20:21
@Japabu
Japabu enabled auto-merge October 22, 2026 20:21
@Japabu
Japabu added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 777c8c5 Oct 22, 2026
4 checks passed
@Japabu
Japabu deleted the wt/toyos-crypto-adopt-ed25519 branch October 22, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant