Files
StarForth/src/starkernel/capsule/capsule_mint.c
T
Robert Allan JamesandClaude Sonnet 5 d00e6cb50d MINT: default personality now loads common:messaging.4th (fixes MSG-TICK error-spam)
capsule_mint_identity()'s MINT_DEFAULT_PERSONALITY only ever defined
WELCOME, unlike capsule_console.c's own CONSOLE_IDENTITY_SRC. The moment
WIREBIND made a freshly-minted identity's VM live, Hera's per-idle-tick
MSG-TICK pump (repl.c Phase C) began erroring on it every tick forever --
UNKNOWN WORD: 'MSG-TICK'. Every identity minted before this fix would hit
the same infinite error-spam on going live, not just this one.

Fixed by loading common:messaging.4th + calling MSG-CD-INIT before
WELCOME, mirroring capsule_console.c's own pattern exactly.

Found and verified minting the first real second identity end to end:
disk/captain-bob.img, hot-attached via QMP alongside an already-
authenticated Zuse session, MINTed, WIREBIND-attached, switched into
live with USE, WELCOME confirmed. Zero MSG-TICK errors over 18+ seconds
of idle-loop cycling after the fix (was immediate and continuous before).
Full three-architecture regression clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBjfeLPo71sUQ8zC7V7P5m
2026-08-28 22:05:55 -04:00

193 lines
8.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef __STARKERNEL__
#error "capsule_mint.c is kernel-only"
#endif
#include "starkernel/capsule_mint.h"
#include "starkernel/homeblocks_sig.h"
#include "starkernel/user_identity_seed.h"
#include "starkernel/x509_ed25519.h"
#include "starkernel/ed25519.h"
#include "starkernel/virtio_rng.h"
#include "block_subsystem.h" /* compute_crc64() */
#include "blkio.h"
#include <string.h>
#include <stddef.h>
/* Fixed layout, devblock offsets from the drive's own start -- see
* capsule_mint.h's own doc comment. */
#define MINT_CERT_DEVBLOCK 2u
#define MINT_CERT_DEVBLOCKS 1u
#define MINT_IDENTITY_SRC_OFFSET 3u
#define MINT_IDENTITY_SRC_DEVBLOCKS 2u /* 1 seed record + 1 personality source */
#define MINT_METADATA_DEVBLOCKS 5u /* devblocks 0..4 reserved */
/* Real, working, minimal default personality -- FABRIC-3.md §F.6/§F.8's
* "default personality content" question stays open (mint-time-fixed vs.
* user-editable), but the identity this mints must actually do something
* when RUNCAP births it, not just exist. Needs the "Block NNNN" header
* capsule_exec_payload() requires (§F.18) -- discovered live, not
* optional. 4999 is an unused block number (see capsules/ block-map).
*
* Loads common:messaging.4th + MSG-CD-INIT exactly like capsule_console.c's
* own CONSOLE_IDENTITY_SRC -- every live VM except Hera owns a MSG-TICK
* word for her per-idle-tick pump to VM-EXEC into (repl.c's Phase C loop).
* A minted identity's RUNCAP-born VM is not exempt from that pump, so
* omitting this left it spamming "UNKNOWN WORD: 'MSG-TICK'" every idle
* tick forever from the moment WIREBIND made the VM live -- found live
* minting a real second identity (FABRIC-3.md §F.26 follow-up). */
static const char MINT_DEFAULT_PERSONALITY[] =
"Block 4999\n"
"S\" common:messaging.4th\" EXEC\n"
"MSG-CD-INIT\n"
": WELCOME ( -- ) .\" Minted identity -- default personality\" CR ;\n"
"WELCOME\n";
/* Write exactly one devblock (4096 bytes) at devblock offset `devblock`,
* as 4 consecutive 1KiB forth-block writes -- same convention
* homeblocks_sig_check()'s own read loop already uses. */
static int write_devblock(struct blkio_dev *dev, uint32_t devblock,
const uint8_t *buf4096) {
uint32_t base = devblock * 4u;
for (uint32_t i = 0; i < 4u; i++) {
if (blkio_write((blkio_dev_t *)dev, base + i,
buf4096 + (size_t)i * BLKIO_FORTH_BLOCK_SIZE) != BLKIO_OK) {
return -1;
}
}
return 0;
}
MintResult capsule_mint_identity(struct blkio_dev *dev, VM *issuer_vm,
const char *full_name, const char *username,
const char *email, const char *phone,
uint8_t out_pubkey[32], uint8_t out_seed[32],
int drive_known_blank) {
if (!dev || !full_name || !username) return MINT_ERR_WRITE_FAIL;
/* full_name/username required and must fit; email/phone may be NULL
* (treated as empty/null) but must fit if given. */
if (full_name[0] == '\0' || strlen(full_name) >= USER_IDENTITY_FULL_NAME_MAX)
return MINT_ERR_INVALID_PROFILE;
if (username[0] == '\0' || strlen(username) >= USER_IDENTITY_USERNAME_MAX)
return MINT_ERR_INVALID_PROFILE;
if (email && strlen(email) >= USER_IDENTITY_EMAIL_MAX)
return MINT_ERR_INVALID_PROFILE;
if (phone && strlen(phone) >= USER_IDENTITY_PHONE_MAX)
return MINT_ERR_INVALID_PROFILE;
/* Refuse to overwrite an already-recognized home-blocks drive --
* mirrors WRITE(10)'s own refuse-on-non-blank-media posture (§F.8,
* decided 2026-08-28: "reasonable by analogy" there, now decided).
* Skipped when the caller already just confirmed HOMEBLOCKS_SIG_BLANK
* itself (drive_known_blank) -- otherwise this repeats the exact same
* full BOT read sequence a second time immediately after the first. */
if (!drive_known_blank) {
homeblocks_sig_t existing;
if (homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &existing)
== HOMEBLOCKS_SIG_OK) {
return MINT_ERR_ALREADY_MINTED;
}
}
/* issuer_vm==NULL is genesis mode (§F.21) -- no existing Zuse to
* require a cert from. */
if (issuer_vm && !issuer_vm->zuse_cert_installed) return MINT_ERR_NO_ZUSE_CERT;
if (!virtio_rng_ready()) return MINT_ERR_NO_ENTROPY;
/* Fresh identity keypair. */
uint8_t seed[32], pubkey[32];
if (virtio_rng_get_bytes(seed, sizeof(seed)) != 0) return MINT_ERR_NO_ENTROPY;
ed25519_keygen(seed, pubkey);
if (out_pubkey) memcpy(out_pubkey, pubkey, 32);
if (out_seed) memcpy(out_seed, seed, 32);
/* Fresh drive_uuid -- an independent random draw, not derived from
* the identity seed (§F.8 decision 3: "which physical drive," not
* "whose identity"). */
uint8_t drive_uuid[16];
if (virtio_rng_get_bytes(drive_uuid, sizeof(drive_uuid)) != 0)
return MINT_ERR_NO_ENTROPY;
uint32_t cert_devblock = 0;
uint32_t cert_devblocks = 0;
if (issuer_vm) {
/* Cert: SubjectPublicKeyInfo = new pubkey, serialNumber =
* drive_uuid, signed by Zuse's own on-device seed (§F.8
* decision 4). Genesis mode (issuer_vm==NULL) skips this
* entirely -- Zuse's own identity isn't verified against a
* separate signer, she's recognized directly by pubkey match
* against zuse_genesis_marker_t (§F.21). */
uint8_t cert[512];
size_t cert_len = x509_build_user_cert(cert, sizeof(cert), pubkey,
drive_uuid, issuer_vm->zuse_cert_seed);
if (cert_len == 0 || cert_len > 4096) return MINT_ERR_CERT_BUILD;
uint8_t cert_block[4096];
memset(cert_block, 0, sizeof(cert_block));
memcpy(cert_block, cert, cert_len);
if (write_devblock(dev, MINT_CERT_DEVBLOCK, cert_block) != 0)
return MINT_ERR_WRITE_FAIL;
cert_devblock = MINT_CERT_DEVBLOCK;
cert_devblocks = MINT_CERT_DEVBLOCKS;
}
/* user_identity_seed_t record. */
user_identity_seed_t idrec;
memset(&idrec, 0, sizeof(idrec));
idrec.magic = USER_IDENTITY_SEED_MAGIC;
idrec.version = USER_IDENTITY_SEED_VERSION;
memcpy(idrec.seed, seed, 32);
memcpy(idrec.pubkey, pubkey, 32);
memcpy(idrec.full_name, full_name, strlen(full_name));
memcpy(idrec.username, username, strlen(username));
if (email) memcpy(idrec.email, email, strlen(email));
if (phone) memcpy(idrec.phone, phone, strlen(phone));
idrec.crc = compute_crc64((const uint8_t *)&idrec, offsetof(user_identity_seed_t, crc));
if (write_devblock(dev, MINT_IDENTITY_SRC_OFFSET, (const uint8_t *)&idrec) != 0)
return MINT_ERR_WRITE_FAIL;
/* Default personality source, in the devblock right after the seed
* record -- RUNCAP reads exactly this layout. */
uint8_t personality_block[4096];
memset(personality_block, 0, sizeof(personality_block));
memcpy(personality_block, MINT_DEFAULT_PERSONALITY, sizeof(MINT_DEFAULT_PERSONALITY) - 1);
if (write_devblock(dev, MINT_IDENTITY_SRC_OFFSET + 1u, personality_block) != 0)
return MINT_ERR_WRITE_FAIL;
/* homeblocks_sig_t -- written last, so a crash/power-loss partway
* through minting never leaves a drive that reads as a valid,
* complete home-blocks drive with missing content behind it. */
homeblocks_sig_t sig;
memset(&sig, 0, sizeof(sig));
sig.magic = HOMEBLOCKS_SIG_PACK(HOMEBLOCKS_SIG_VERSION_0);
memcpy(sig.drive_uuid, drive_uuid, 16);
sig.minted_time_ns = 0; /* no monotonic-ns source exists
* anywhere in this codebase yet,
* §F.8's own open item */
sig.metadata_devblocks = MINT_METADATA_DEVBLOCKS;
sig.cert_offset = cert_devblock;
sig.cert_devblocks = cert_devblocks;
sig.identity_src_offset = MINT_IDENTITY_SRC_OFFSET;
sig.identity_src_devblocks = MINT_IDENTITY_SRC_DEVBLOCKS;
sig.hdr_crc = homeblocks_sig_compute_crc(&sig);
uint8_t sig_block[4096];
memcpy(sig_block, &sig, sizeof(sig));
if (write_devblock(dev, HOMEBLOCKS_SIG_START_FBLOCK / 4u, sig_block) != 0)
return MINT_ERR_WRITE_FAIL;
(void)blkio_flush((blkio_dev_t *)dev);
return MINT_OK;
}