/- IP.Crypto.SHA256 — FIPS 180-4 SHA-256. This file lands the **pure-data SHA-256 algorithm** in Lean, validated against NIST test vectors at sim time. The Signal-side hardware engine (iterative 64-cycle compressor with a 64×32-bit message-schedule register file) follows in Phase L.1.b — but having the pure-data reference here gives Ed25519 / secp256k1 / HMAC-SHA256 a callable hash to wire into their own sim tests in the meantime, and provides the spec the HW engine will be cross-checked against. Layout (per FIPS 180-4 §6.2): * 8 working-state words a..h (each 32 bits) * 64 round constants K[t] * 64 message-schedule words W[t]: first 16 come from the input block (512-bit message block = 16 × 32-bit), rest computed via the schedule recurrence. Per-round update (t = 0..63): T1 = h + Σ₁(e) + Ch(e,f,g) + K[t] + W[t] T2 = Σ₀(a) + Maj(a,b,c) (a, b, c, d, e, f, g, h) := (T1 + T2, a, b, c, d + T1, e, f, g) Helpers: Ch(x,y,z) = (x ∧ y) ⊕ (¬x ∧ z) Maj(x,y,z) = (x ∧ y) ⊕ (x ∧ z) ⊕ (y ∧ z) Σ₀(x) = ROTR(x, 2) ⊕ ROTR(x, 13) ⊕ ROTR(x, 22) Σ₁(x) = ROTR(x, 6) ⊕ ROTR(x, 11) ⊕ ROTR(x, 25) σ₀(x) = ROTR(x, 7) ⊕ ROTR(x, 18) ⊕ SHR(x, 3) σ₁(x) = ROTR(x, 17) ⊕ ROTR(x, 19) ⊕ SHR(x, 10) -/ import Sparkle import Sparkle.Core.Lut open Sparkle.Core (kLutMacro) open Sparkle.Core.Domain open Sparkle.Core.Signal namespace Sparkle.IP.Crypto.SHA256 /-! ### Pure-data helpers. -/ /-- 32-bit rotate-right: ROTR(x, n) = (x >>> n) | (x <<< (32-n)). -/ @[inline] def rotr32 (x : BitVec 32) (n : Nat) : BitVec 32 := (x >>> (BitVec.ofNat 32 n)) ||| (x <<< (BitVec.ofNat 32 (32 - n))) @[inline] def shr32 (x : BitVec 32) (n : Nat) : BitVec 32 := x >>> (BitVec.ofNat 32 n) @[inline] def bigSigma0 (x : BitVec 32) : BitVec 32 := (rotr32 x 2) ^^^ (rotr32 x 13) ^^^ (rotr32 x 22) @[inline] def bigSigma1 (x : BitVec 32) : BitVec 32 := (rotr32 x 6) ^^^ (rotr32 x 11) ^^^ (rotr32 x 25) @[inline] def smallSigma0 (x : BitVec 32) : BitVec 32 := (rotr32 x 7) ^^^ (rotr32 x 18) ^^^ (shr32 x 3) @[inline] def smallSigma1 (x : BitVec 32) : BitVec 32 := (rotr32 x 17) ^^^ (rotr32 x 19) ^^^ (shr32 x 10) @[inline] def chFn (x y z : BitVec 32) : BitVec 32 := (x &&& y) ^^^ ((~~~x) &&& z) @[inline] def majFn (x y z : BitVec 32) : BitVec 32 := (x &&& y) ^^^ (x &&& z) ^^^ (y &&& z) /-! ### K table — 64 round constants. -/ def kTable : Array (BitVec 32) := #[ 0x428a2f98#32, 0x71374491#32, 0xb5c0fbcf#32, 0xe9b5dba5#32, 0x3956c25b#32, 0x59f111f1#32, 0x923f82a4#32, 0xab1c5ed5#32, 0xd807aa98#32, 0x12835b01#32, 0x243185be#32, 0x550c7dc3#32, 0x72be5d74#32, 0x80deb1fe#32, 0x9bdc06a7#32, 0xc19bf174#32, 0xe49b69c1#32, 0xefbe4786#32, 0x0fc19dc6#32, 0x240ca1cc#32, 0x2de92c6f#32, 0x4a7484aa#32, 0x5cb0a9dc#32, 0x76f988da#32, 0x983e5152#32, 0xa831c66d#32, 0xb00327c8#32, 0xbf597fc7#32, 0xc6e00bf3#32, 0xd5a79147#32, 0x06ca6351#32, 0x14292967#32, 0x27b70a85#32, 0x2e1b2138#32, 0x4d2c6dfc#32, 0x53380d13#32, 0x650a7354#32, 0x766a0abb#32, 0x81c2c92e#32, 0x92722c85#32, 0xa2bfe8a1#32, 0xa81a664b#32, 0xc24b8b70#32, 0xc76c51a3#32, 0xd192e819#32, 0xd6990624#32, 0xf40e3585#32, 0x106aa070#32, 0x19a4c116#32, 0x1e376c08#32, 0x2748774c#32, 0x34b0bcb5#32, 0x391c0cb3#32, 0x4ed8aa4a#32, 0x5b9cca4f#32, 0x682e6ff3#32, 0x748f82ee#32, 0x78a5636f#32, 0x84c87814#32, 0x8cc70208#32, 0x90befffa#32, 0xa4506ceb#32, 0xbef9a3f7#32, 0xc67178f2#32 ] /-- Initial hash values H0..H7 (FIPS 180-4 §5.3.3). -/ def initH : Array (BitVec 32) := #[ 0x6a09e667#32, 0xbb67ae85#32, 0x3c6ef372#32, 0xa54ff53a#32, 0x510e527f#32, 0x9b05688c#32, 0x1f83d9ab#32, 0x5be0cd19#32 ] /-! ### Pure-data single-block compression. -/ /-- Compute W[t] (t = 0..63) given an array of 16 input 32-bit words. -/ def expandW (block : Array (BitVec 32)) : Array (BitVec 32) := Id.run do let mut w : Array (BitVec 32) := Array.replicate 64 (0#32) for i in [:16] do w := w.set! i (block.getD i 0#32) for i in [16:64] do let s0 := smallSigma0 (w.getD (i - 15) 0#32) let s1 := smallSigma1 (w.getD (i - 2) 0#32) let v := s1 + w.getD (i - 7) 0#32 + s0 + w.getD (i - 16) 0#32 w := w.set! i v return w /-- Pure-data SHA-256 single-block compression. Takes initial state H (8 words) and a 16-word message block, returns the post-compression state. -/ def compressBlock (h : Array (BitVec 32)) (block : Array (BitVec 32)) : Array (BitVec 32) := Id.run do let w := expandW block let mut a := h.getD 0 0#32 let mut b := h.getD 1 0#32 let mut c := h.getD 2 0#32 let mut d := h.getD 3 0#32 let mut e := h.getD 4 0#32 let mut f := h.getD 5 0#32 let mut g := h.getD 6 0#32 let mut hh := h.getD 7 0#32 for t in [:64] do let t1 := hh + bigSigma1 e + chFn e f g + kTable.getD t 0#32 + w.getD t 0#32 let t2 := bigSigma0 a + majFn a b c hh := g g := f f := e e := d + t1 d := c c := b b := a a := t1 + t2 return #[h.getD 0 0#32 + a, h.getD 1 0#32 + b, h.getD 2 0#32 + c, h.getD 3 0#32 + d, h.getD 4 0#32 + e, h.getD 5 0#32 + f, h.getD 6 0#32 + g, h.getD 7 0#32 + hh] /-- Pure-data SHA-256 of a list of pre-padded message blocks. Each block is a 16-word array. Padding is the caller's responsibility for now (FIPS 180-4 §5.1.1 — append 1 bit + zeros + 64-bit length). -/ def hashBlocks (blocks : List (Array (BitVec 32))) : Array (BitVec 32) := blocks.foldl compressBlock initH /-- Pure-data SHA-256 of a byte array. Performs the FIPS 180-4 padding internally. -/ def sha256OfBytes (input : Array UInt8) : Array (BitVec 32) := Id.run do -- Append 0x80, then zeros, then 64-bit length (in bits). let bitLen : Nat := input.size * 8 let mut lenBytes : Array UInt8 := #[] for i in [:8] do let shift := (7 - i) * 8 let byte := (bitLen >>> shift) &&& 0xFF lenBytes := lenBytes.push (UInt8.ofNat byte) -- Append 0x80 + as many zero bytes as needed so total -- (input + 0x80 + zeros + 8 length-bytes) is a multiple -- of 64. let mut padded : Array UInt8 := input padded := padded.push 0x80 while padded.size % 64 ≠ 56 do padded := padded.push 0x00 for b in lenBytes do padded := padded.push b -- Convert padded to 16-word blocks. let nBlocks := padded.size / 64 let mut blocks : List (Array (BitVec 32)) := [] for blk in [:nBlocks] do let mut words : Array (BitVec 32) := #[] for i in [:16] do let off := blk * 64 + i * 4 let w0 := (padded.getD off 0).toNat let w1 := (padded.getD (off + 1) 0).toNat let w2 := (padded.getD (off + 2) 0).toNat let w3 := (padded.getD (off + 3) 0).toNat let word : Nat := (w0 <<< 24) ||| (w1 <<< 16) ||| (w2 <<< 8) ||| w3 words := words.push (BitVec.ofNat 32 word) blocks := blocks ++ [words] return hashBlocks blocks /-! ### Signal-side helpers — Sparkle-synth-friendly. -/ /-- Signal-side 32-bit rotate-right by a compile-time constant `n`. Built from `>>>` and `<<<` over 32-bit BitVec constants, both recognised by Sparkle's IR elaborator. -/ @[inline] def rotr32Sig {dom : DomainConfig} (x : Signal dom (BitVec 32)) (n : Nat) : Signal dom (BitVec 32) := let nBv : BitVec 32 := BitVec.ofNat 32 n let nBv' : BitVec 32 := BitVec.ofNat 32 (32 - n) -- (x >>> n) ||| (x <<< (32 - n)) let pn : Signal dom (BitVec 32) := Signal.pure nBv let pn' : Signal dom (BitVec 32) := Signal.pure nBv' let rs : Signal dom (BitVec 32) := x >>> pn let ls : Signal dom (BitVec 32) := x <<< pn' rs ||| ls @[inline] def shr32Sig {dom : DomainConfig} (x : Signal dom (BitVec 32)) (n : Nat) : Signal dom (BitVec 32) := let nBv : BitVec 32 := BitVec.ofNat 32 n let pn : Signal dom (BitVec 32) := Signal.pure nBv x >>> pn @[inline] def bigSigma0Sig {dom : DomainConfig} (x : Signal dom (BitVec 32)) : Signal dom (BitVec 32) := let a := rotr32Sig x 2 let b := rotr32Sig x 13 let c := rotr32Sig x 22 let ab := a ^^^ b ab ^^^ c @[inline] def bigSigma1Sig {dom : DomainConfig} (x : Signal dom (BitVec 32)) : Signal dom (BitVec 32) := let a := rotr32Sig x 6 let b := rotr32Sig x 11 let c := rotr32Sig x 25 let ab := a ^^^ b ab ^^^ c @[inline] def smallSigma0Sig {dom : DomainConfig} (x : Signal dom (BitVec 32)) : Signal dom (BitVec 32) := let a := rotr32Sig x 7 let b := rotr32Sig x 18 let c := shr32Sig x 3 let ab := a ^^^ b ab ^^^ c @[inline] def smallSigma1Sig {dom : DomainConfig} (x : Signal dom (BitVec 32)) : Signal dom (BitVec 32) := let a := rotr32Sig x 17 let b := rotr32Sig x 19 let c := shr32Sig x 10 let ab := a ^^^ b ab ^^^ c @[inline] def chFnSig {dom : DomainConfig} (x y z : Signal dom (BitVec 32)) : Signal dom (BitVec 32) := let xy : Signal dom (BitVec 32) := x &&& y let nx : Signal dom (BitVec 32) := ~~~x let nxz : Signal dom (BitVec 32) := nx &&& z xy ^^^ nxz @[inline] def majFnSig {dom : DomainConfig} (x y z : Signal dom (BitVec 32)) : Signal dom (BitVec 32) := let xy : Signal dom (BitVec 32) := x &&& y let xz : Signal dom (BitVec 32) := x &&& z let yz : Signal dom (BitVec 32) := y &&& z let t1 : Signal dom (BitVec 32) := xy ^^^ xz t1 ^^^ yz /-! ### K-table mux: pick K[t] from a 7-bit counter. -/ @[hardware_module] def kMux {dom : DomainConfig} (cntSig : Signal dom (BitVec 7)) : Signal dom (BitVec 32) := -- Use the `kLut!` macro to expand a 64-way constant-table -- mux into a fully-unrolled `Signal.mux` chain at term- -- elab time. An `Id.run do`-based loop here would have -- left a `let mut` Lean expression the IR elaborator -- can't unfold ("Cannot synthesise Id.run"). See -- `Sparkle/Core/Lut.lean` for the macro definition. kLut! cntSig [ Signal.pure 0x428a2f98#32, Signal.pure 0x71374491#32, Signal.pure 0xb5c0fbcf#32, Signal.pure 0xe9b5dba5#32, Signal.pure 0x3956c25b#32, Signal.pure 0x59f111f1#32, Signal.pure 0x923f82a4#32, Signal.pure 0xab1c5ed5#32, Signal.pure 0xd807aa98#32, Signal.pure 0x12835b01#32, Signal.pure 0x243185be#32, Signal.pure 0x550c7dc3#32, Signal.pure 0x72be5d74#32, Signal.pure 0x80deb1fe#32, Signal.pure 0x9bdc06a7#32, Signal.pure 0xc19bf174#32, Signal.pure 0xe49b69c1#32, Signal.pure 0xefbe4786#32, Signal.pure 0x0fc19dc6#32, Signal.pure 0x240ca1cc#32, Signal.pure 0x2de92c6f#32, Signal.pure 0x4a7484aa#32, Signal.pure 0x5cb0a9dc#32, Signal.pure 0x76f988da#32, Signal.pure 0x983e5152#32, Signal.pure 0xa831c66d#32, Signal.pure 0xb00327c8#32, Signal.pure 0xbf597fc7#32, Signal.pure 0xc6e00bf3#32, Signal.pure 0xd5a79147#32, Signal.pure 0x06ca6351#32, Signal.pure 0x14292967#32, Signal.pure 0x27b70a85#32, Signal.pure 0x2e1b2138#32, Signal.pure 0x4d2c6dfc#32, Signal.pure 0x53380d13#32, Signal.pure 0x650a7354#32, Signal.pure 0x766a0abb#32, Signal.pure 0x81c2c92e#32, Signal.pure 0x92722c85#32, Signal.pure 0xa2bfe8a1#32, Signal.pure 0xa81a664b#32, Signal.pure 0xc24b8b70#32, Signal.pure 0xc76c51a3#32, Signal.pure 0xd192e819#32, Signal.pure 0xd6990624#32, Signal.pure 0xf40e3585#32, Signal.pure 0x106aa070#32, Signal.pure 0x19a4c116#32, Signal.pure 0x1e376c08#32, Signal.pure 0x2748774c#32, Signal.pure 0x34b0bcb5#32, Signal.pure 0x391c0cb3#32, Signal.pure 0x4ed8aa4a#32, Signal.pure 0x5b9cca4f#32, Signal.pure 0x682e6ff3#32, Signal.pure 0x748f82ee#32, Signal.pure 0x78a5636f#32, Signal.pure 0x84c87814#32, Signal.pure 0x8cc70208#32, Signal.pure 0x90befffa#32, Signal.pure 0xa4506ceb#32, Signal.pure 0xbef9a3f7#32, Signal.pure 0xc67178f2#32 ] /-! ### SHA-256 HW engine — iterative 64-cycle compressor. Pipeline contract: cycle 0 : `start` pulse; `blockIn` carries the full 512-bit message block (W0 in MSB). cycle 1..64 : compression round t = 0..63, fed by the rolling 16-word W buffer. cycle 65 : add a..h into H0..H7 and pulse `done`. cycle ≥66 : output `hash` holds the final 256-bit digest; `done` returns to 0. Buffer strategy: keep a single 512-bit register representing W[t-16..t-1] (MSB = oldest = W[t-16], LSB = newest = W[t-1]). Each cycle: - Read W[t] from the MSB position (or from the new slot for t < 16: directly from `blockIn[t]`). - Compute next W (only used for t < 48 since the last 16 rounds don't generate new W values, but the shift is harmless). - Shift the buffer left by 32 bits, place new W in the low 32 bits. -/ structure SHA256Out (dom : DomainConfig) where /-- 256-bit packed hash output (H0 in MSB). -/ hash : Signal dom (BitVec 256) /-- Pulses high for one cycle after the 64-round compression completes. -/ done : Signal dom Bool instance {dom : DomainConfig} : Sparkle.Core.HasDomain (SHA256Out dom) dom := ⟨⟩ def sha256Block {dom : DomainConfig} (start : Signal dom Bool) (blockIn : Signal dom (BitVec 512)) (first : Signal dom Bool := Signal.pure true) : SHA256Out dom := circuit do -- 7-bit round counter (0..65, plus idle at 0). let cnt ← Signal.reg (0#7) -- 8 working-state registers — initialised to initH on -- the start pulse. let aR ← Signal.reg (0#32) let bR ← Signal.reg (0#32) let cR ← Signal.reg (0#32) let dR ← Signal.reg (0#32) let eR ← Signal.reg (0#32) let fR ← Signal.reg (0#32) let gR ← Signal.reg (0#32) let hR ← Signal.reg (0#32) -- 8 final-hash registers (carry IV plus accumulated -- block deltas). let h0R ← Signal.reg (0x6a09e667#32) let h1R ← Signal.reg (0xbb67ae85#32) let h2R ← Signal.reg (0x3c6ef372#32) let h3R ← Signal.reg (0xa54ff53a#32) let h4R ← Signal.reg (0x510e527f#32) let h5R ← Signal.reg (0x9b05688c#32) let h6R ← Signal.reg (0x1f83d9ab#32) let h7R ← Signal.reg (0x5be0cd19#32) -- W-buffer: 16 × 32-bit slots, MSB = oldest. let wBuf ← Signal.reg (0#512) -- `done` strobe. let doneR ← Signal.reg false let cntSig := (cnt : Signal dom (BitVec 7)) let aSig := (aR : Signal dom (BitVec 32)) let bSig := (bR : Signal dom (BitVec 32)) let cSig := (cR : Signal dom (BitVec 32)) let dSig := (dR : Signal dom (BitVec 32)) let eSig := (eR : Signal dom (BitVec 32)) let fSig := (fR : Signal dom (BitVec 32)) let gSig := (gR : Signal dom (BitVec 32)) let hSig := (hR : Signal dom (BitVec 32)) let h0Sig := (h0R : Signal dom (BitVec 32)) let h1Sig := (h1R : Signal dom (BitVec 32)) let h2Sig := (h2R : Signal dom (BitVec 32)) let h3Sig := (h3R : Signal dom (BitVec 32)) let h4Sig := (h4R : Signal dom (BitVec 32)) let h5Sig := (h5R : Signal dom (BitVec 32)) let h6Sig := (h6R : Signal dom (BitVec 32)) let h7Sig := (h7R : Signal dom (BitVec 32)) let wBufSig := (wBuf : Signal dom (BitVec 512)) -- W-buffer layout: 16 slots × 32 bits = 512 bits. -- slot k = bits [ (15-k)*32 .. (16-k)*32 - 1 ] from LSB. -- So slot 0 occupies the TOP 32 bits (high end of the -- 512-bit reg), slot 15 the BOTTOM 32 bits. -- -- Convention while running round t (using the original -- FIPS naming where we're computing W[t+16] from W[t].. -- W[t+15] still in the buffer): -- slot 0 = W[t] ← consumed this round as W input -- slot 1 = W[t+1] = W[(t+16)-15] for σ₀ -- slot 9 = W[t+9] = W[(t+16)-7] direct addend -- slot 14 = W[t+14] = W[(t+16)-2] for σ₁ -- slot 15 = W[t+15] = W[(t+16)-1] (unused) -- SHA-256 bit functions are FULLY INLINED at their call sites -- below. A named top-level `Signal → Signal` def (`rotr32Sig`, -- `bigSigma1Sig`, …) is opaque to the synth elaborator -- ("not inlinable"), AND wrapping the applicative body in a -- local `let f := fun x => …` lambda that RETURNS a `<$>/<*>` -- chain also fails ("Cannot instantiate Seq.seq") — the -- elaborator only lowers applicative expressions written -- directly, not returned from a lambda. So each Σ/σ/Ch/Maj is -- spelled out inline where it is consumed (see `sig1`, `sig0`, -- `chv`, `majv`, `newW1`, `newW2`). `rotrK x n` and `shrK x n` -- macros would help but the elaborator needs the literal form. -- Convert "slot k" → `BitVec.extractLsb' ((15-k)*32) 32`. let wt := wBufSig.map (BitVec.extractLsb' 480 32 ·) -- slot 0 let wTm15 := wBufSig.map (BitVec.extractLsb' 448 32 ·) -- slot 1 let wTm7 := wBufSig.map (BitVec.extractLsb' 192 32 ·) -- slot 9 let wTm2 := wBufSig.map (BitVec.extractLsb' 32 32 ·) -- slot 14 let wTm16 := wt -- alias for clarity in the recurrence -- Round-counter predicates. let p0_7 := (Signal.pure 0#7 : Signal dom (BitVec 7)) let p1_7 := (Signal.pure 1#7 : Signal dom (BitVec 7)) let p65_7 := (Signal.pure 65#7 : Signal dom (BitVec 7)) let isIdle := cntSig === p0_7 let isFinish := cntSig === p65_7 -- Compute T1, T2, next-state. Round t runs at cnt = t+1 (cnt 1..64), so -- the round constant is K[cnt-1] — feed cnt-1 (NOT cnt) to the K table. let kt := kMux (cntSig - (Signal.pure 1#7 : Signal dom (BitVec 7))) -- Σ₁(e) = ROTR(e,6) ⊕ ROTR(e,11) ⊕ ROTR(e,25), inlined. let e6 := (eSig >>> (Signal.pure 6#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let e6l := (eSig <<< (Signal.pure 26#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let rE6 := (e6 ||| e6l : Signal dom (BitVec 32)) let e11 := (eSig >>> (Signal.pure 11#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let e11l := (eSig <<< (Signal.pure 21#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let rE11 := (e11 ||| e11l : Signal dom (BitVec 32)) let e25 := (eSig >>> (Signal.pure 25#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let e25l := (eSig <<< (Signal.pure 7#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let rE25 := (e25 ||| e25l : Signal dom (BitVec 32)) let sig1ab := (rE6 ^^^ rE11 : Signal dom (BitVec 32)) let sig1 := (sig1ab ^^^ rE25 : Signal dom (BitVec 32)) -- Σ₀(a) = ROTR(a,2) ⊕ ROTR(a,13) ⊕ ROTR(a,22), inlined. let a2 := (aSig >>> (Signal.pure 2#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let a2l := (aSig <<< (Signal.pure 30#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let rA2 := (a2 ||| a2l : Signal dom (BitVec 32)) let a13 := (aSig >>> (Signal.pure 13#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let a13l := (aSig <<< (Signal.pure 19#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let rA13 := (a13 ||| a13l : Signal dom (BitVec 32)) let a22 := (aSig >>> (Signal.pure 22#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let a22l := (aSig <<< (Signal.pure 10#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let rA22 := (a22 ||| a22l : Signal dom (BitVec 32)) let sig0ab := (rA2 ^^^ rA13 : Signal dom (BitVec 32)) let sig0 := (sig0ab ^^^ rA22 : Signal dom (BitVec 32)) -- Ch(e,f,g) = (e ∧ f) ⊕ (¬e ∧ g), inlined. let chXy := (eSig &&& fSig : Signal dom (BitVec 32)) let chNx := (~~~eSig : Signal dom (BitVec 32)) let chNxz := (chNx &&& gSig : Signal dom (BitVec 32)) let chv := (chXy ^^^ chNxz : Signal dom (BitVec 32)) -- Maj(a,b,c) = (a∧b) ⊕ (a∧c) ⊕ (b∧c), inlined. let mjXy := (aSig &&& bSig : Signal dom (BitVec 32)) let mjXz := (aSig &&& cSig : Signal dom (BitVec 32)) let mjYz := (bSig &&& cSig : Signal dom (BitVec 32)) let mjT1 := (mjXy ^^^ mjXz : Signal dom (BitVec 32)) let majv := (mjT1 ^^^ mjYz : Signal dom (BitVec 32)) let t1a := hSig + sig1 let t1b := t1a + chv let t1c := t1b + kt let t1 := t1c + wt let t2 := sig0 + majv -- Next-cycle round registers. When in a compression -- round (cnt 1..64), shift a→b→c→d, e gets d+t1, etc. -- On start, reload a..h from H-state (so a 2nd block -- continues from H0..H7, not from initH — initH is -- pre-loaded into the H regs at reset time). -- On a `first` block, load a..h from the SHA-256 IV (initH) instead of the -- carried H-state, so the module hashes a fresh message. (Enables re-use -- for the many independent hashes in HMAC / RFC-6979 without a hard reset.) let firstSig := (first : Signal dom Bool) let aLoad := Signal.mux firstSig (Signal.pure 0x6a09e667#32) h0Sig let bLoad := Signal.mux firstSig (Signal.pure 0xbb67ae85#32) h1Sig let cLoad := Signal.mux firstSig (Signal.pure 0x3c6ef372#32) h2Sig let dLoad := Signal.mux firstSig (Signal.pure 0xa54ff53a#32) h3Sig let eLoad := Signal.mux firstSig (Signal.pure 0x510e527f#32) h4Sig let fLoad := Signal.mux firstSig (Signal.pure 0x9b05688c#32) h5Sig let gLoad := Signal.mux firstSig (Signal.pure 0x1f83d9ab#32) h6Sig let hLoadV := Signal.mux firstSig (Signal.pure 0x5be0cd19#32) h7Sig let aNext := t1 + t2 let eNext := dSig + t1 aR <~ Signal.mux start aLoad (Signal.mux isIdle aSig aNext) bR <~ Signal.mux start bLoad (Signal.mux isIdle bSig aSig) cR <~ Signal.mux start cLoad (Signal.mux isIdle cSig bSig) dR <~ Signal.mux start dLoad (Signal.mux isIdle dSig cSig) eR <~ Signal.mux start eLoad (Signal.mux isIdle eSig eNext) fR <~ Signal.mux start fLoad (Signal.mux isIdle fSig eSig) gR <~ Signal.mux start gLoad (Signal.mux isIdle gSig fSig) hR <~ Signal.mux start hLoadV (Signal.mux isIdle hSig gSig) -- H0..H7 update: at cycle 65 (isFinish), add a..h into -- H0..H7. Hold otherwise (initH already loaded on -- reset). let h0Acc := h0Sig + aSig let h1Acc := h1Sig + bSig let h2Acc := h2Sig + cSig let h3Acc := h3Sig + dSig let h4Acc := h4Sig + eSig let h5Acc := h5Sig + fSig let h6Acc := h6Sig + gSig let h7Acc := h7Sig + hSig -- On a `first`-block start, (re)load H0..H7 with the IV so the finish-time -- accumulation `H += a..h` starts from initH. Otherwise carry / accumulate. let startFirst := (start &&& firstSig : Signal dom Bool) h0R <~ Signal.mux startFirst (Signal.pure 0x6a09e667#32) (Signal.mux isFinish h0Acc h0Sig) h1R <~ Signal.mux startFirst (Signal.pure 0xbb67ae85#32) (Signal.mux isFinish h1Acc h1Sig) h2R <~ Signal.mux startFirst (Signal.pure 0x3c6ef372#32) (Signal.mux isFinish h2Acc h2Sig) h3R <~ Signal.mux startFirst (Signal.pure 0xa54ff53a#32) (Signal.mux isFinish h3Acc h3Sig) h4R <~ Signal.mux startFirst (Signal.pure 0x510e527f#32) (Signal.mux isFinish h4Acc h4Sig) h5R <~ Signal.mux startFirst (Signal.pure 0x9b05688c#32) (Signal.mux isFinish h5Acc h5Sig) h6R <~ Signal.mux startFirst (Signal.pure 0x1f83d9ab#32) (Signal.mux isFinish h6Acc h6Sig) h7R <~ Signal.mux startFirst (Signal.pure 0x5be0cd19#32) (Signal.mux isFinish h7Acc h7Sig) -- W-buffer update: on start, load with the first 16 -- words from blockIn. During cnt 1..48, compute -- newW = σ₁(wTm2) + wTm7 + σ₀(wTm15) + wTm16, shift -- the buffer left by 32 bits, place newW in the bottom. -- During cnt 49..64 we still shift; the consumed wt -- is what we use this round. Stop shifting at finish. -- σ₁(wTm2) = ROTR(x,17) ⊕ ROTR(x,19) ⊕ SHR(x,10), inlined. let w2r17 := (wTm2 >>> (Signal.pure 17#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w2r17l := (wTm2 <<< (Signal.pure 15#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w2R17 := (w2r17 ||| w2r17l : Signal dom (BitVec 32)) let w2r19 := (wTm2 >>> (Signal.pure 19#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w2r19l := (wTm2 <<< (Signal.pure 13#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w2R19 := (w2r19 ||| w2r19l : Signal dom (BitVec 32)) let w2s10 := (wTm2 >>> (Signal.pure 10#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w2ab := (w2R17 ^^^ w2R19 : Signal dom (BitVec 32)) let newW1 := (w2ab ^^^ w2s10 : Signal dom (BitVec 32)) -- σ₀(wTm15) = ROTR(x,7) ⊕ ROTR(x,18) ⊕ SHR(x,3), inlined. let w15r7 := (wTm15 >>> (Signal.pure 7#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w15r7l := (wTm15 <<< (Signal.pure 25#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w15R7 := (w15r7 ||| w15r7l : Signal dom (BitVec 32)) let w15r18 := (wTm15 >>> (Signal.pure 18#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w15r18l := (wTm15 <<< (Signal.pure 14#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w15R18 := (w15r18 ||| w15r18l : Signal dom (BitVec 32)) let w15s3 := (wTm15 >>> (Signal.pure 3#32 : Signal dom (BitVec 32)) : Signal dom (BitVec 32)) let w15ab := (w15R7 ^^^ w15R18 : Signal dom (BitVec 32)) let newW2 := (w15ab ^^^ w15s3 : Signal dom (BitVec 32)) let n1n2 := newW1 + wTm7 let n3 := n1n2 + newW2 let newW := n3 + wTm16 -- Shift the buffer left by 32 (drop the top 32 bits), -- then prepend new word into low position. -- Extract bits [479:0] (= old bits [479:0], shifted to -- [511:32]) and append newW [31:0]. let bufLow := wBufSig.map (BitVec.extractLsb' 0 480 ·) let shiftedBuf := bufLow ++ newW wBuf <~ Signal.mux start blockIn (Signal.mux (~~~isIdle) shiftedBuf wBufSig) -- Counter: 0 → 1 on start, +1 each cycle while non-zero, -- 0 again after finish (cycle 65). let cntInc := cntSig + p1_7 cnt <~ Signal.mux start p1_7 (Signal.mux isFinish p0_7 (Signal.mux isIdle p0_7 cntInc)) doneR <~ isFinish -- Pack hash output. let h01 := h0Sig ++ h1Sig let h23 := h2Sig ++ h3Sig let h45 := h4Sig ++ h5Sig let h67 := h6Sig ++ h7Sig let h0123 := h01 ++ h23 let h4567 := h45 ++ h67 let hAll := h0123 ++ h4567 return ({ hash := hAll , done := (doneR : Signal dom Bool) } : SHA256Out dom) end Sparkle.IP.Crypto.SHA256