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 {
|
2026-04-16 17:17:10 +00:00
|
|
|
|
// 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 {
|
2026-04-16 17:17:10 +00:00
|
|
|
|
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 {
|
2026-04-16 17:17:10 +00:00
|
|
|
|
// 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 {
|
2026-04-16 17:17:10 +00:00
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|