1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
nescript/examples/sha256/computing_state.ne

77 lines
2.8 KiB
Text
Raw Normal View History

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
// 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. The
// schedule_one / round_one APIs both want a byte
// offset (= word_index * 4), so track that directly
// instead of recomputing `index << 2` per iteration:
// the byte offset is just incremented by 4 in lieu
// of the index by 1.
//
// First byte offset for this phase: (16 + phase*4)*4
// = 64 + phase * 16.
var w_byte: u8 = 64 + (cp_phase << 4)
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
var step: u8 = 0
while step < BATCH_SIZE {
schedule_one(w_byte)
w_byte += 4
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
step += 1
}
cp_phase += 1
} else if cp_phase < FOLD_PHASE {
// Round batch. First byte offset for this phase:
// (phase - SCHED_PHASES) * 4 * 4
// = (phase - SCHED_PHASES) << 4.
var kw_byte: u8 = (cp_phase - SCHED_PHASES) << 4
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
var step2: u8 = 0
while step2 < BATCH_SIZE {
round_one(kw_byte)
kw_byte += 4
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
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()
}
}