feat(kernel): sk_v4_mint_zuse -- the real Zuse minted onto a second stick, read back before the marker names her (#92, 4.5) [skip ci]
Asks for a stick, shows its size, writes nothing until YES; then gpt_write_one, the partition's first devblocks zeroed, a fresh Zuse minted, read back, and only then the marker's real key. Any failure before the marker: 'failed -- nothing changed'. The gate remembers the last stick it turned away as a spare, offered first by the next mint. Builds on three ISAs; not yet called. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
This commit is contained in:
@@ -355,7 +355,7 @@ Tasks 1-3 accepted and merged (#159, master 594d2f93); #155 merged with them.
|
||||
- [x] 4.2 run `mint_zuse` on amd64 before any kernel change: FAIL, "UNKNOWN WORD: 'MINT-ZUSE'" (logs/20261108-175928); the prompt check tightened to need the minted line first
|
||||
- [x] 4.3 `gpt_write_one()` in `kernel/src/gpt.c` (one partition, protective MBR, both headers): builds clean on three ISAs; checked on the host (a throwaway harness, not in the tree) -- 600 MiB and 32 MiB blank images and the pair's used Zuse stick each give one identity partition, `gpt_read` reads it back, `sfdisk --verify` clean. Not yet called.
|
||||
- [x] 4.4 `sk_v4_identity_offer_wait()` / `sk_v4_identity_recheck_offered()`: a stick offered, not attached, while a mint waits (`g_mint_waiting`, `g_offered` in `sk_v4_identity.c`; any stick not this system's and readable -- blank, foreign or a bad table; ends on a stick, a key, or the system stick leaving). Builds on three ISAs; not yet called.
|
||||
- [ ] 4.5 `sk_v4_mint.c`: the flow -- plug-in line, size line, `YES`, GPT, zeroed, minted, read back, marker
|
||||
- [x] 4.5 `sk_v4_mint.c`: the flow -- plug-in line, size line, `YES`, GPT, zeroed, minted, read back, marker. Builds on three ISAs; not yet called. The gate gained a **spare**: the last stick turned away as not this system's (or handed back by a refused mint) is offered first by the next mint (for `mint_zuse_refuse`: "a second `MINT-ZUSE` is offered again"); and accessors `sk_v4_identity_stick_in/offered_present/release_offered/marker/set_marker`.
|
||||
- [ ] 4.6 `MINT-ZUSE` in `sk_v4_words[]` (request 2): word_count 317 -> 318
|
||||
- [ ] 4.7 the real Zuse stick recognised and attached after the system stick; her session
|
||||
- [ ] 4.8 `mint_zuse` passes on amd64; then the other tests: `mint_zuse_refuse`, `mint_pull_system`, `zuse_stick_first`, `mint_small`, the pair's stick offered
|
||||
|
||||
+2
-2
@@ -728,8 +728,8 @@ V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard
|
||||
V4_ROM_C := $(BUILD_DIR)/v4_rom_64.c
|
||||
V4_MAP_C := $(BUILD_DIR)/v4_map_64.c
|
||||
|
||||
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c $(KERNEL_SRC)/v4/sk_v4_marker.c $(KERNEL_SRC)/v4/sk_v4_grow.c
|
||||
KERNEL_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c $(KERNEL_SRC)/v4/sk_v4_marker.c $(KERNEL_SRC)/v4/sk_v4_grow.c
|
||||
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c $(KERNEL_SRC)/v4/sk_v4_marker.c $(KERNEL_SRC)/v4/sk_v4_grow.c $(KERNEL_SRC)/v4/sk_v4_mint.c
|
||||
KERNEL_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c $(KERNEL_SRC)/v4/sk_v4_marker.c $(KERNEL_SRC)/v4/sk_v4_grow.c $(KERNEL_SRC)/v4/sk_v4_mint.c
|
||||
# v4/system/boot.c is the boot the hosted v4 system runs too: the ROM
|
||||
# assembles the system, then the capsules from this kernel's own capsule
|
||||
# directory, then the prompt.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "uefi.h" /* BootDisk */
|
||||
#include "blkio.h"
|
||||
#include "starkernel/v4/sk_v4_marker.h"
|
||||
|
||||
/* After POST and the block chain: every xHCI controller brought up, and the
|
||||
* heartbeat started, as the v3 path does before its headless wait. `boot_disk`
|
||||
@@ -38,6 +39,16 @@ void sk_v4_identity_check_now(void);
|
||||
* leaves: 0. */
|
||||
int sk_v4_identity_offer_wait(blkio_dev_t **raw, uint64_t *bytes);
|
||||
|
||||
/* For the mint: the system stick's Artemis is attached; the offered stick is
|
||||
* still plugged in; the offered stick handed back unused (a mint refused or
|
||||
* stopped -- the next mint offers it again); the genesis marker as read, and
|
||||
* as the mint has just written it. */
|
||||
int sk_v4_identity_stick_in(void);
|
||||
int sk_v4_identity_offered_present(void);
|
||||
void sk_v4_identity_release_offered(void);
|
||||
const sk_v4_marker_t *sk_v4_identity_marker(void);
|
||||
void sk_v4_identity_set_marker(const sk_v4_marker_t *m);
|
||||
|
||||
/* The arrival check run again on the offered stick, now that the mint has
|
||||
* written its GPT and identity (it is then recognised as the real Zuse's). */
|
||||
void sk_v4_identity_recheck_offered(void);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* sk_v4_mint.h -- MINT-ZUSE: the real Zuse minted onto a second stick
|
||||
* (docs/v4.0.0/STICK.md 6a; plan 2026-10-29-stick-5, Task 4).
|
||||
*
|
||||
* Built only with STARFORTH_V4=1 (Kconfig.kernel).
|
||||
*/
|
||||
#ifndef STARKERNEL_V4_SK_V4_MINT_H
|
||||
#define STARKERNEL_V4_SK_V4_MINT_H
|
||||
|
||||
/* The whole of STICK.md 6a's minting: asks for a stick, shows its size, writes
|
||||
* nothing until YES, then a GPT and a fresh Zuse identity on it, read back,
|
||||
* and only then the real key in the genesis marker. 0 when she is minted;
|
||||
* -1 otherwise, with its line said and the marker as it was. Run by the
|
||||
* MINT-ZUSE word, and (Task 5) the fixed screen's R. */
|
||||
int sk_v4_mint_zuse(void);
|
||||
|
||||
#endif /* STARKERNEL_V4_SK_V4_MINT_H */
|
||||
@@ -100,6 +100,9 @@ static uint64_t g_last_beat;
|
||||
* not refused; it is remembered here until the mint is done with it */
|
||||
static int g_mint_waiting;
|
||||
static sk_v4_drive_t *g_offered;
|
||||
/* the last stick turned away as not this system's (or handed back by a mint
|
||||
* that was refused): still plugged in, so a mint offers it first */
|
||||
static sk_v4_drive_t *g_spare;
|
||||
static const BootDisk *g_boot_disk; /* the loader's: the disk it booted from */
|
||||
static int g_stick_in; /* a stick's Artemis partition is attached */
|
||||
|
||||
@@ -297,6 +300,7 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d)
|
||||
}
|
||||
if (gpt_rc != GPT_OK || !sk_v4_is_this_stick(nparts)) {
|
||||
console_println("USB: a drive that is not this system's stick -- not used");
|
||||
if (gpt_rc != GPT_EIO) g_spare = d;
|
||||
return 1;
|
||||
}
|
||||
if (sk_v4_grow_stick(d, &nparts) != 0) {
|
||||
@@ -370,6 +374,7 @@ static void sk_v4_drive_left(sk_v4_drive_t *d)
|
||||
blkio_dev_t *dev = d->used;
|
||||
|
||||
if (d == g_offered) g_offered = (sk_v4_drive_t *)0; /* the offered stick, pulled */
|
||||
if (d == g_spare) g_spare = (sk_v4_drive_t *)0;
|
||||
|
||||
if (dev) {
|
||||
d->used = (blkio_dev_t *)0;
|
||||
@@ -457,7 +462,8 @@ int sk_v4_identity_offer_wait(blkio_dev_t **raw, uint64_t *bytes)
|
||||
{
|
||||
int got = 0;
|
||||
|
||||
g_offered = (sk_v4_drive_t *)0;
|
||||
g_offered = g_spare; /* one already plugged in, turned away */
|
||||
g_spare = (sk_v4_drive_t *)0;
|
||||
g_mint_waiting = 1;
|
||||
for (;;) {
|
||||
sk_v4_identity_beat();
|
||||
@@ -474,6 +480,32 @@ int sk_v4_identity_offer_wait(blkio_dev_t **raw, uint64_t *bytes)
|
||||
return got;
|
||||
}
|
||||
|
||||
int sk_v4_identity_stick_in(void)
|
||||
{
|
||||
return g_stick_in;
|
||||
}
|
||||
|
||||
int sk_v4_identity_offered_present(void)
|
||||
{
|
||||
return g_offered != (sk_v4_drive_t *)0;
|
||||
}
|
||||
|
||||
void sk_v4_identity_release_offered(void)
|
||||
{
|
||||
g_spare = g_offered;
|
||||
g_offered = (sk_v4_drive_t *)0;
|
||||
}
|
||||
|
||||
const sk_v4_marker_t *sk_v4_identity_marker(void)
|
||||
{
|
||||
return &g_marker;
|
||||
}
|
||||
|
||||
void sk_v4_identity_set_marker(const sk_v4_marker_t *m)
|
||||
{
|
||||
g_marker = *m;
|
||||
}
|
||||
|
||||
void sk_v4_identity_recheck_offered(void)
|
||||
{
|
||||
sk_v4_drive_t *d = g_offered;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/* sk_v4_mint.c -- MINT-ZUSE: the real Zuse minted onto a second stick
|
||||
* (docs/v4.0.0/STICK.md 6a; plan 2026-10-29-stick-5, Task 4).
|
||||
*
|
||||
* The stick is asked for, offered (sk_v4_identity_offer_wait), its size
|
||||
* shown, and nothing is written until YES is typed. Then a GPT with one
|
||||
* StarshipOS identity partition (gpt_write_one), the partition's first
|
||||
* devblocks zeroed (a used stick's old bytes must not read as an identity), a
|
||||
* fresh Zuse minted into it, read back -- and only then the real key written
|
||||
* into the genesis marker on the system stick's Artemis. Any failure before
|
||||
* the marker leaves the marker as it was: the old Zuse stick, if there is
|
||||
* one, still works.
|
||||
*/
|
||||
#include "starkernel/v4/sk_v4_mint.h"
|
||||
#include "starkernel/v4/sk_v4_identity.h"
|
||||
#include "starkernel/v4/sk_v4_marker.h"
|
||||
#include "starkernel/capsule_zuse_boot.h"
|
||||
#include "starkernel/homeblocks_sig.h"
|
||||
#include "starkernel/kbd_input.h"
|
||||
#include "starkernel/console.h"
|
||||
#include "starkernel/arch.h"
|
||||
#include "starkernel/gpt.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MINT_MIN_BYTES (64ULL * 1024ULL * 1024ULL) /* a stick under 64 MiB is refused */
|
||||
#define MINT_IDENT_SECTORS (500ULL * 2048ULL) /* the identity partition: 500 MiB */
|
||||
#define MINT_ZERO_FBLOCKS 32u /* its first 8 devblocks, zeroed */
|
||||
|
||||
/* "MINT-ZUSE: a stick of <N> MiB -- all of it is erased" */
|
||||
static void say_size(uint64_t bytes)
|
||||
{
|
||||
static char line[80];
|
||||
char num[24];
|
||||
uint64_t mib = bytes / (1024ULL * 1024ULL);
|
||||
int n = 0, i = 0;
|
||||
|
||||
do { num[n++] = (char)('0' + (int)(mib % 10u)); mib /= 10u; } while (mib && n < 23);
|
||||
memcpy(line, "MINT-ZUSE: a stick of ", 22);
|
||||
i = 22;
|
||||
while (n) line[i++] = num[--n];
|
||||
memcpy(line + i, " MiB -- all of it is erased", 28); /* with its NUL */
|
||||
console_println(line);
|
||||
}
|
||||
|
||||
/* A line typed at the keyboard or the serial line, echoed, while the beat
|
||||
* runs. 1 when Enter ended it; 0 when the system stick or the offered stick
|
||||
* left first. */
|
||||
static int read_answer(char *buf, int max)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
for (;;) {
|
||||
int c = sk_input_getc();
|
||||
if (c < 0) {
|
||||
if (!sk_v4_identity_stick_in() || !sk_v4_identity_offered_present()) return 0;
|
||||
sk_v4_identity_beat();
|
||||
arch_halt();
|
||||
continue;
|
||||
}
|
||||
if (c == '\r' || c == '\n') { console_putc('\n'); buf[len] = '\0'; return 1; }
|
||||
if ((c == 8 || c == 127) && len > 0) { len--; console_puts("\b \b"); continue; }
|
||||
if (c >= 32 && c < 127 && len < max - 1) { buf[len++] = (char)c; console_putc((char)c); }
|
||||
}
|
||||
}
|
||||
|
||||
static int failed(void)
|
||||
{
|
||||
console_println("MINT-ZUSE: failed -- nothing changed");
|
||||
sk_v4_identity_release_offered();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sk_v4_mint_zuse(void)
|
||||
{
|
||||
static blkio_dev_t part;
|
||||
static gpt_part_dev_t part_state;
|
||||
static gpt_part_t parts[4];
|
||||
static uint8_t zero[BLKIO_FORTH_BLOCK_SIZE];
|
||||
blkio_dev_t *raw;
|
||||
uint64_t bytes, sectors;
|
||||
uint8_t guid[16], seed[32], pub[32], rseed[32], rpub[32];
|
||||
homeblocks_sig_t sig;
|
||||
homeblocks_sig_result_t sig_rc;
|
||||
sk_v4_marker_t m;
|
||||
unsigned n, i;
|
||||
int pi, rc;
|
||||
char answer[8];
|
||||
|
||||
console_println("MINT-ZUSE: plug in the stick for the real Zuse");
|
||||
if (!sk_v4_identity_offer_wait(&raw, &bytes)) {
|
||||
console_println("MINT-ZUSE: stopped -- nothing written");
|
||||
return -1;
|
||||
}
|
||||
if (bytes < MINT_MIN_BYTES) {
|
||||
console_println("MINT-ZUSE: the stick is under 64 MiB");
|
||||
sk_v4_identity_release_offered();
|
||||
return -1;
|
||||
}
|
||||
say_size(bytes);
|
||||
console_println("MINT-ZUSE: type YES to write it");
|
||||
if (!read_answer(answer, (int)sizeof answer) || strcmp(answer, "YES") != 0) {
|
||||
console_println("MINT-ZUSE: stopped -- nothing written");
|
||||
if (sk_v4_identity_offered_present()) sk_v4_identity_release_offered();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* the stick: one identity partition, 500 MiB or the stick less 2 MiB */
|
||||
sectors = bytes / 512u;
|
||||
sectors = sectors - 4096u < MINT_IDENT_SECTORS ? sectors - 4096u : MINT_IDENT_SECTORS;
|
||||
rc = gpt_write_one(raw, gpt_type_starship_identity, sectors, guid);
|
||||
if (rc == GPT_BAD) {
|
||||
console_println("MINT-ZUSE: the stick's sectors are not 512 bytes");
|
||||
sk_v4_identity_release_offered();
|
||||
return -1;
|
||||
}
|
||||
if (rc != GPT_OK || gpt_read(raw, parts, 4u, &n) != GPT_OK) return failed();
|
||||
pi = gpt_find(parts, n, gpt_type_starship_identity);
|
||||
if (pi < 0 || gpt_part_open(&part, &part_state, raw, &parts[pi]) != BLKIO_OK) return failed();
|
||||
|
||||
/* nothing of the stick's old bytes may read as an identity */
|
||||
for (i = 0; i < MINT_ZERO_FBLOCKS; i++)
|
||||
if (blkio_write(&part, i, zero) != BLKIO_OK) return failed();
|
||||
|
||||
/* Zuse, minted, then read back from the stick */
|
||||
if (capsule_zuse_boot_genesis_mint(&part, seed, pub) != 0) return failed();
|
||||
sig_rc = homeblocks_sig_check(&part, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
|
||||
if (capsule_zuse_boot_check_key(&part, sig_rc, &sig, pub, rseed, rpub) != ZUSE_DRIVE_HERS ||
|
||||
memcmp(rseed, seed, 32) != 0)
|
||||
return failed();
|
||||
|
||||
/* only now the marker: the proxy kept, the real Zuse named */
|
||||
m = *sk_v4_identity_marker();
|
||||
if (!m.present) return failed();
|
||||
memcpy(m.real, pub, 32);
|
||||
m.has_real = 1;
|
||||
if (sk_v4_marker_write(&m) != 0) return failed();
|
||||
sk_v4_identity_set_marker(&m);
|
||||
memset(seed, 0, sizeof seed);
|
||||
memset(rseed, 0, sizeof rseed);
|
||||
|
||||
console_println("Zuse: real Zuse minted onto the second stick");
|
||||
sk_v4_identity_recheck_offered();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user