Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 104 additions & 30 deletions .github/workflows/eosim-sanity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ on:
workflow_dispatch:

env:
EOSIM_VERSION: "0.1.0"
# v0.1.0 has never existed. embeddedos-org/EoSim tags v1.0.0 through
# v3.0.1, and no release publishes a wheel -- so the pip install below
# 404s and this workflow has never had a green run. v1.5.0 is the
# release marked Latest.
EOSIM_VERSION: "1.5.0"

permissions:
contents: read
Expand All @@ -30,13 +34,47 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install EoSim
run: pip install "eosim @ https://github.com/embeddedos-org/EoSim/releases/download/v${{ env.EOSIM_VERSION }}/eosim-${{ env.EOSIM_VERSION }}-py3-none-any.whl"
run: git clone --depth 1 --branch v${{ env.EOSIM_VERSION }} https://github.com/embeddedos-org/EoSim.git /tmp/EoSim && pip install /tmp/EoSim
- name: Verify installation
run: |
# Assert, do not print. The tag and the package's declared version
# disagree upstream -- v1.5.0 ships "eosim, version 2.0.0" -- so
# pinning to a tag does not pin what the name suggests, and nothing
# here would notice if the tag moved. Raised against EoSim
# separately; asserted here so this workflow stops being the place
# it goes unnoticed.
eosim --version
eosim list
eosim --version | grep -qE "2\.0\.0" || {
echo "::error::eosim --version is not the expected 2.0.0 for tag v${{ env.EOSIM_VERSION }}"
eosim --version
exit 1
}
# platforms/ is data, not package data: PLATFORMS_DIR resolves to
# <site-packages>/platforms while the wheel ships only
# eosim/platforms/__init__.py. Without this copy `eosim list` prints
# "Available platforms (0)" and exits 0 -- so the step named
# "Validate all platform configs" validated nothing and passed.
- name: Install platform data
run: |
SITE_PACKAGES=$(python -c "import eosim, os; print(os.path.dirname(os.path.dirname(eosim.__file__)))")
cp -r /tmp/EoSim/platforms "$SITE_PACKAGES/"
- name: Validate all platform configs
run: eosim list && eosim doctor
run: |
eosim doctor
# Assert a non-zero count rather than trusting the exit code:
# `eosim list` exits 0 with zero platforms, which is how this step
# passed while checking nothing.
python - <<'PY'
import os, sys
from eosim.platforms import discover_platforms
import eosim, pathlib
root = pathlib.Path(eosim.__file__).parent.parent / "platforms"
found = discover_platforms(str(root))
print(f"discovered {len(found)} platforms under {root}")
if not found:
sys.exit("::error::no platform configs discovered; "
"this step would otherwise pass having validated nothing")
PY

nested-simulation:
name: Nested Simulation (${{ matrix.platform }})
Expand All @@ -60,10 +98,14 @@ jobs:
python-version: "3.12"
- name: Install EoSim
run: |
pip install "eosim @ https://github.com/embeddedos-org/EoSim/releases/download/v${{ env.EOSIM_VERSION }}/eosim-${{ env.EOSIM_VERSION }}-py3-none-any.whl"
SITE_PACKAGES=$(python -c "import eosim; import os; print(os.path.dirname(os.path.dirname(eosim.__file__)))")
git clone --depth 1 https://github.com/embeddedos-org/EoSim.git /tmp/eosim-data
cp -r /tmp/eosim-data/platforms "$SITE_PACKAGES/"
# Install from a git checkout: no release ships a wheel.
git clone --depth 1 --branch v${{ env.EOSIM_VERSION }} \
https://github.com/embeddedos-org/EoSim.git /tmp/EoSim
pip install /tmp/EoSim
# platforms/ is data, not package data, so `eosim list` reports
# zero platforms without it. Copy it from the same checkout.
SITE_PACKAGES=$(python -c "import eosim, os; print(os.path.dirname(os.path.dirname(eosim.__file__)))")
cp -r /tmp/EoSim/platforms "$SITE_PACKAGES/"
- name: Simulate ${{ matrix.platform }}
run: |
eosim run ${{ matrix.platform }} --headless --timeout 10
Expand All @@ -90,10 +132,12 @@ jobs:
python-version: "3.12"
- name: Install EoSim
run: |
pip install "eosim @ https://github.com/embeddedos-org/EoSim/releases/download/v${{ env.EOSIM_VERSION }}/eosim-${{ env.EOSIM_VERSION }}-py3-none-any.whl"
git clone --depth 1 --branch v${{ env.EOSIM_VERSION }} https://github.com/embeddedos-org/EoSim.git /tmp/EoSim && pip install /tmp/EoSim
SITE_PACKAGES=$(python -c "import eosim; import os; print(os.path.dirname(os.path.dirname(eosim.__file__)))")
git clone --depth 1 https://github.com/embeddedos-org/EoSim.git /tmp/eosim-data
cp -r /tmp/eosim-data/platforms "$SITE_PACKAGES/"
# From the same pinned checkout. A second, unpinned clone here meant
# the package came from v${{ env.EOSIM_VERSION }} while platforms/
# came from whatever the default branch pointed at that morning.
cp -r /tmp/EoSim/platforms "$SITE_PACKAGES/"
- name: Boot guest and test EoSim inside
run: |
echo "=== Nested Guest: ${{ matrix.guest }} ==="
Expand All @@ -109,12 +153,26 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# `eosim list` exits 0 with zero platforms, so "list && doctor" passed
# on both these runners while validating nothing. Copy platforms/ from
# the same pinned checkout and assert a non-zero count, as
# install-validate now does.
- name: Test EoSim on Windows
shell: bash
run: |
pip install "eosim @ https://github.com/embeddedos-org/EoSim/releases/download/v${{ env.EOSIM_VERSION }}/eosim-${{ env.EOSIM_VERSION }}-py3-none-any.whl"
git clone --depth 1 --branch v${{ env.EOSIM_VERSION }} https://github.com/embeddedos-org/EoSim.git /tmp/EoSim && pip install /tmp/EoSim
SITE_PACKAGES=$(python -c "import eosim, os; print(os.path.dirname(os.path.dirname(eosim.__file__)))")
cp -r /tmp/EoSim/platforms "$SITE_PACKAGES/"
eosim --version
eosim list && eosim doctor
eosim list
eosim doctor
python -c "
import pathlib, sys, eosim
from eosim.platforms import discover_platforms
root = pathlib.Path(eosim.__file__).parent.parent / 'platforms'
found = discover_platforms(str(root))
print(f'discovered {len(found)} platforms')
sys.exit(0 if found else 'no platform configs discovered')
"

macos-sanity:
name: macOS Sanity
Expand All @@ -126,30 +184,46 @@ jobs:
with:
python-version: "3.12"
- name: Test EoSim on macOS
shell: bash
run: |
pip install "eosim @ https://github.com/embeddedos-org/EoSim/releases/download/v${{ env.EOSIM_VERSION }}/eosim-${{ env.EOSIM_VERSION }}-py3-none-any.whl"
git clone --depth 1 --branch v${{ env.EOSIM_VERSION }} https://github.com/embeddedos-org/EoSim.git /tmp/EoSim && pip install /tmp/EoSim
SITE_PACKAGES=$(python -c "import eosim, os; print(os.path.dirname(os.path.dirname(eosim.__file__)))")
cp -r /tmp/EoSim/platforms "$SITE_PACKAGES/"
eosim --version
eosim list && eosim doctor
eosim list
eosim doctor
python -c "
import pathlib, sys, eosim
from eosim.platforms import discover_platforms
root = pathlib.Path(eosim.__file__).parent.parent / 'platforms'
found = discover_platforms(str(root))
print(f'discovered {len(found)} platforms')
sys.exit(0 if found else 'no platform configs discovered')
"

sanity-gate:
name: EoSim Sanity Gate
if: always()
needs: [install-validate, nested-simulation, nested-guest-install, windows-sanity, macos-sanity]
runs-on: ubuntu-latest
steps:
- name: Results
# Iterates toJSON(needs) rather than naming one dependency. The
# previous body tested install-validate alone and then printed "All
# EoSim sanity checks passed" -- which it would do with the other four
# jobs red. This workflow has never had a green run, so this gate is
# about to start mattering for the first time; a summary that asserts
# more than it checked is the wrong thing to start with.
- name: Every job in this workflow must have succeeded
env:
RESULTS: ${{ toJSON(needs) }}
run: |
echo "════════════════════════════════════════════════"
echo " EoSim Sanity Results"
echo "════════════════════════════════════════════════"
echo "Install & Validate (3 OS × 3 Py): ${{ needs.install-validate.result }}"
echo "Nested Simulation (7 platforms): ${{ needs.nested-simulation.result }}"
echo "Nested Guest Install (3 guests): ${{ needs.nested-guest-install.result }}"
echo "Windows Sanity: ${{ needs.windows-sanity.result }}"
echo "macOS Sanity: ${{ needs.macos-sanity.result }}"
echo "════════════════════════════════════════════════"
if [ "${{ needs.install-validate.result }}" != "success" ]; then
echo "❌ Install/validate failed"; exit 1
printf '%s\n' "$RESULTS"
bad=$(printf '%s' "$RESULTS" | jq -r '
to_entries[]
| select(.value.result != "success")
| " \(.key): \(.value.result)"')
if [ -n "$bad" ]; then
echo "::error::EoSim Sanity Gate failed. These jobs did not succeed:"
printf '%s\n' "$bad"
exit 1
fi
echo "All EoSim sanity checks passed"
echo "All EoSim sanity jobs succeeded."
56 changes: 12 additions & 44 deletions core/ed25519_verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@ static int point_is_identity(gf p[4])
return diff == 0;
}

static void scalarbase(gf r[4], const uint8_t *s)
{
gf q[4];
fe_copy16(q[0], BX);
fe_copy16(q[1], BY);
fe_copy16(q[2], gf1);
fe_mul(q[3], BX, BY);
scalarmult(r, q, s);
}

/* Reject a public key outside the prime-order subgroup.
*
* Decoding a point is not enough. Ed25519 has eight points of low order, and
Expand All @@ -304,51 +314,9 @@ static int point_is_identity(gf p[4])
* so there is no separate constant to transcribe wrongly: a mistyped L would
* reject valid keys, and only in the field.
*
* A arrives negated from unpackneg(). [L](-A) = -[L]A and the identity is its
* own negation, so neither condition is affected by the sign.
*
* Formulation taken from eBoot#57 by @muhammadburhandevv-hub, which reached
* this before I did and states both conditions in one expression.
* The key arrives negated from unpackneg(). [L](-A) = -[L]A and the identity
* is its own negation, so neither condition is affected by the sign.
*/
static int key_has_prime_order(gf A[4])
{
uint8_t order_l[32];
gf q[4], multiple[4];
int i;

for (i = 0; i < 32; i++)
order_l[i] = (uint8_t)ORDER_L[i];
for (i = 0; i < 4; i++)
fe_copy16(q[i], A[i]);

scalarmult(multiple, q, order_l);
return point_is_identity(multiple) && !point_is_identity(A);
}

static void scalarbase(gf r[4], const uint8_t *s)
{
gf q[4];
fe_copy16(q[0], BX);
fe_copy16(q[1], BY);
fe_copy16(q[2], gf1);
fe_mul(q[3], BX, BY);
scalarmult(r, q, s);
}

static int point_is_identity(gf p[4])
{
uint8_t encoded[32];
point_pack(encoded, p);

uint8_t diff = (uint8_t)(encoded[0] ^ 1U);
for (int i = 1; i < 32; i++)
diff |= encoded[i];
return diff == 0;
}

/* Public keys must be non-identity points in Ed25519's prime-order subgroup.
* Merely decoding a point is insufficient: an identity or torsion key can
* make the verification equation true without knowledge of a private key. */
static int public_key_is_valid_subgroup(gf public_key[4])
{
uint8_t order_l[32];
Expand Down
16 changes: 10 additions & 6 deletions include/eos_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, tlv_hash) +

/* Every remaining field, pinned.
*
* Four of the fourteen fields were asserted. Transposing two adjacent
* Three of the thirteen field offsets were asserted (the fourth pre-existing
* assert is sizeof, which is not a field). Transposing two adjacent
* same-width fields moves neither sizeof nor any of those four offsets, so it
* compiled clean: with load_addr and entry_addr swapped, all four existing
* asserts still passed and the bootloader would load an image at its entry
Expand All @@ -132,15 +133,18 @@ EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, flags) == 24,
"flags must stay at offset 24");
EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, sig_len) == 61,
"sig_len must stay at offset 61");
EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, reserved) == 62,
"reserved[] must stay at offset 62");
/* tlv_len and tlv_hash are asserted above, where #93 introduced them; the
* 30 bytes they occupy are the ones this block used to pin as reserved[]. */

/* Field widths. An offset assert cannot see a field growing into padding that
* happens to keep every later offset -- reserved[] absorbs exactly that. */
* happens to keep every later offset -- the 30 bytes at 62 absorb exactly
* that, which is why both halves of that span carry a width assert. */
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->hash) == 32,
"hash[] is 32 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->reserved) == 30,
"reserved[] is 30 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_len) == 2,
"tlv_len is 2 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_hash) == 28,
"tlv_hash is 28 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->signature) == 64,
"signature[] is 64 bytes on the wire");

Expand Down
Loading
Loading