rumpk/libs/membrane/fs/monolith.nim

117 lines
4.0 KiB
Nim

# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Nexus Membrane: The Monolith (4MB Key)
##
## Implements the Zero-Friction Encryption per SPEC-021.
## - L0 (Factory): Unprotected 4MB random key
## - L1 (Sovereignty): Password-protected (Argon2id + XChaCha20)
import ../blk
const
MONOLITH_SECTOR_START* = 2048'u64 # After bootloader
MONOLITH_SIZE* = 4 * 1024 * 1024 # 4MB = 4,194,304 bytes
MONOLITH_SECTORS* = MONOLITH_SIZE div 512 # 8192 sectors
# Magic header to detect encrypted vs raw Monolith
MAGIC_ENCRYPTED* = 0xFFFFFFFF'u32
MAGIC_RAW* = 0x4D4F4E4F'u32 # "MONO"
type
MonolithHeader* = object
magic*: uint32
version*: uint32
salt*: array[32, byte] # Argon2id salt (if encrypted)
nonce*: array[24, byte] # XChaCha20-Poly1305 nonce
reserved*: array[448, byte]
# Static buffer for Monolith (4MB in RAM)
# Note: This is heavy but necessary for single-copy unlock
var monolith_buffer: array[MONOLITH_SIZE, byte]
var monolith_loaded: bool = false
# ION Client for Crypto
import ../ion_client
# TODO: Add XChaCha20-Poly1305 and Argon2id imports when implementing Phase B (lock/unlock)
proc monolith_read*(): bool =
## Read 4MB Monolith from disk into RAM buffer
## Returns: true on success
for i in 0'u64 ..< MONOLITH_SECTORS:
let offset = int(i * 512)
if blk_read(MONOLITH_SECTOR_START + i, addr monolith_buffer[offset]) < 0:
return false
monolith_loaded = true
return true
proc monolith_get_header*(): ptr MonolithHeader =
## Get pointer to the Monolith header (first 512 bytes)
if not monolith_loaded: return nil
return cast[ptr MonolithHeader](addr monolith_buffer[0])
proc monolith_is_encrypted*(): bool =
## Check if the Monolith is password-protected
if not monolith_loaded: return false
let hdr = monolith_get_header()
return hdr.magic == MAGIC_ENCRYPTED
proc monolith_derive_volume_key*(out_key: ptr array[32, byte]) =
## Derive VolumeKey = BLAKE3(Monolith)
## This is the master key for SFS encryption
if not monolith_loaded: return
# Skip header (512 bytes), hash the rest
let data_start = addr monolith_buffer[512]
let data_len = uint64(MONOLITH_SIZE - 512)
out_key[] = crypto_blake3(data_start, data_len)
proc monolith_generate*(dest_sectors: uint64 = MONOLITH_SECTOR_START): bool =
## Generate a new random Monolith (called during `nexus forge --image`)
## Note: In real impl, we'd use HAL RNG. Here we use a simple PRNG placeholder.
# Initialize header
var hdr: MonolithHeader
hdr.magic = MAGIC_RAW
hdr.version = 1
# Write header
copyMem(addr monolith_buffer[0], addr hdr, sizeof(MonolithHeader))
# Fill rest with "random" data (placeholder - real impl uses hal_crypto_random)
# For now, use a simple LCG seeded by a counter
var seed: uint64 = 0xDEADBEEF12345678'u64
for i in 512 ..< MONOLITH_SIZE:
seed = seed * 6364136223846793005'u64 + 1442695040888963407'u64
monolith_buffer[i] = byte(seed shr 56)
# Write to disk
for i in 0'u64 ..< MONOLITH_SECTORS:
let offset = int(i * 512)
if blk_write(dest_sectors + i, addr monolith_buffer[offset]) < 0:
return false
discard blk_sync()
monolith_loaded = true
return true
# =========================================================
# Phase B: Sovereignty Claim (Future Implementation)
# =========================================================
# proc monolith_lock*(password: string): bool
# ## Encrypt Monolith with user password
# ## 1. Derive key = Argon2id(password, random_salt)
# ## 2. Encrypt Monolith = XChaCha20-Poly1305(raw_data, key)
# ## 3. Write encrypted Monolith with MAGIC_ENCRYPTED header
#
# proc monolith_unlock*(password: string): bool
# ## Decrypt Monolith with user password
# ## 1. Read header, extract salt + nonce
# ## 2. Derive key = Argon2id(password, salt)
# ## 3. Decrypt data = XChaCha20-Poly1305.open(ciphertext, key, nonce)
# ## 4. Verify Poly1305 tag; return false if invalid