1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-09 01:16:12 +00:00
nescript/examples/sha256/computing_state.ne
Claude ba23f8578a
examples/sha256: interactive SHA-256 hasher with on-screen keyboard
An end-to-end FIPS 180-4 SHA-256 hasher running entirely on the NES.
The player types up to 16 ASCII characters on a 5x8 on-screen
keyboard, presses Enter, and the program computes and displays the
64-character hex digest.

Layout (`examples/sha256/*.ne`):
  constants.ne         layout + K[64] / H_INIT[8] tables
                       (declared as `var` with init_array because the
                       v0.1 compiler treats `const u8[N] = [...]` as
                       a no-op — noted in the file)
  assets.ne            44-tile Tileset (A..Z, 0..9, punctuation,
                       special keys, cursor) shared between BG and
                       sprite layers
  background.ne        static nametable (title, labels, keyboard
                       grid) painted at reset
  state.ne             globals
  sha_core.ne          32-bit byte primitives (copy, xor, and, add,
                       not, rotr, shr) in inline asm + sigma/Sigma
                       mixers + schedule/round steps + fold
  render.ne            OAM helpers for cursor, input buffer, and
                       64-nibble digest
  keyboard.ne          key dispatch table
  entering_state.ne    cursor navigation + typing + auto-demo
  computing_state.ne   phased driver (48 schedule steps + 64 rounds
                       + fold across ~30 frames at 4 iterations each)
  showing_state.ne     renders the 256-bit digest as 8 rows of 8
                       sprite glyphs

Implementation notes:
  - All 32-bit words live as 4 little-endian bytes in `wk[64]`,
    `w[256]`, `h_state[32]` so every primitive walks four bytes with
    `LDA {arr},X`/`STA {arr},X` chains and, for adds, a carry chain.
  - Every primitive reads its parameters straight out of the
    transport slots `$04`/`$05` rather than `{dst}`/`{src}`
    substitutions: the inline-asm resolver looks parameters up in
    the analyzer's allocation table but the codegen spills them to a
    different per-function RAM slot, so `{dst}` would resolve to a
    ZP slot nothing ever writes to. Bypassing the substitution
    entirely sidesteps the issue without a compiler change.
  - Rotate-right by any amount is a byte-rotate loop plus a bit-
    rotate loop so the 10 SHA amounts (2, 6, 7, 11, 13, 17, 18, 19,
    22, 25) all compile to a handful of chained `ROR`s.
  - The headless jsnes golden auto-types "NES" after 1 s of idle and
    captures its SHA-256 digest
    AE9145DB5CABC41FE34B54E34AF8881F462362EA20FD8F861B26532FFBB84E0D
    — byte-identical to `shasum` / `hashlib.sha256(b"NES")`.

Build: `cargo run --release -- build examples/sha256.ne`

https://claude.ai/code/session_01FRmSBruVWCufm3LsUVMs8v
2026-04-16 14:02:58 +00:00

68 lines
2.5 KiB
Text
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.

// sha256/computing_state.ne — runs the SHA-256 block compression.
//
// The compression is split across frames so the main loop keeps
// responding to the NMI handshake. Each frame advances one
// "phase"; every phase does a batch of 4 iterations so the
// whole compression (48 schedule steps + 64 rounds + fold)
// finishes inside ~30 frames — roughly half a second of
// wall-clock wait between pressing Enter and the hash appearing.
//
// Phase map:
// 0..11 schedule W[16..63] in batches of 4 (12 × 4 = 48)
// 12..27 rounds 0..63 in batches of 4 (16 × 4 = 64)
// 28 fold wk[A..H] into h_state, transition to Showing
const SCHED_PHASES: u8 = 12 // 12 × 4 = 48 schedule steps
const ROUND_PHASES: u8 = 16 // 16 × 4 = 64 rounds
const FOLD_PHASE: u8 = 28 // SCHED + ROUND
const BATCH_SIZE: u8 = 4 // iterations per frame
state Computing {
on enter {
// Reset persistent hash state and build the padded
// block from the user's message. Phased work then runs
// on top of the freshly-initialised w[] / h_state /
// wk[A..H].
reset_hash_state()
build_padded_block()
init_abcdefgh()
cp_phase = 0
}
on frame {
if cp_phase < SCHED_PHASES {
// Each schedule phase handles BATCH_SIZE words.
// First word index for this phase: 16 + phase * 4.
var first_idx: u8 = 16 + (cp_phase << 2)
var step: u8 = 0
while step < BATCH_SIZE {
var i: u8 = first_idx + step
schedule_one(i << 2) // byte offset into w[]
step += 1
}
cp_phase += 1
} else if cp_phase < FOLD_PHASE {
// Round batch. First round for this phase:
// (phase - SCHED_PHASES) * 4.
var first_r: u8 = (cp_phase - SCHED_PHASES) << 2
var step2: u8 = 0
while step2 < BATCH_SIZE {
var r: u8 = first_r + step2
round_one(r << 2) // K/W share byte 4*i
step2 += 1
}
cp_phase += 1
} else {
// Fold a..h into h_state and hand off to Showing.
fold_abcdefgh()
transition Showing
}
// Draw the input buffer so the user can see what they
// typed while the hash is being computed. The cursor
// sprite is deliberately not drawn here — the keyboard
// is inactive during this phase.
draw_input()
}
}