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:
Claude
2026-11-08 18:14:54 +00:00
parent cfa60fd0ec
commit 8b83e0a72d
6 changed files with 206 additions and 4 deletions
@@ -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
View File
@@ -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);
+16
View File
@@ -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 */
+33 -1
View File
@@ -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;
+143
View File
@@ -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;
}