src/hub_sha/sha256.bend
mylsm-lsm-store@0.5.0.0 · 199 lines · 0 definitions
1# Vendored from bend-collections 0x9ee2e9a299991dcc089fe22c7f3ceb5f
2# (src/crypto/sha/sha256.bend), byte-identical. See core.bend header.
3import Base
4import mylsm-lsm-store@0.5.0.0/src/hub_sha/core.bend as Core
5import mylsm-lsm-store@0.5.0.0/src/hub_sha/state.bend as S
6import bend-kit-bytes@0.3.2.0/bytes.bend as Packed
7
8# The core refinement theorem quantifies over every constant table.
9# Instantiate the verified core with the FIPS 180-4 constants.
10def constants() -> List<&2, U32>:
11 [1116352408, 1899447441, 3049323471, 3921009573,
12 961987163, 1508970993, 2453635748, 2870763221,
13 3624381080, 310598401, 607225278, 1426881987,
14 1925078388, 2162078206, 2614888103, 3248222580,
15 3835390401, 4022224774, 264347078, 604807628,
16 770255983, 1249150122, 1555081692, 1996064986,
17 2554220882, 2821834349, 2952996808, 3210313671,
18 3336571891, 3584528711, 113926993, 338241895,
19 666307205, 773529912, 1294757372, 1396182291,
20 1695183700, 1986661051, 2177026350, 2456956037,
21 2730485921, 2820302411, 3259730800, 3345764771,
22 3516065817, 3600352804, 4094571909, 275423344,
23 430227734, 506948616, 659060556, 883997877,
24 958139571, 1322822218, 1537002063, 1747873779,
25 1955562222, 2024104815, 2227730452, 2361852424,
26 2428436474, 2756734187, 3204031479, 3329325298]
27
28# SHA-256 for the reference octet-list input.
29def sha256(bytes: List<&2, U32>) -> List<&2, U32>:
30 Core.sha256(bytes)
31
32# Convert ASCII text to the reference octet-list input.
33def ascii(text: String) -> List<&2, U32>:
34 Core.ascii(text)
35
36# Format eight digest words as lowercase hexadecimal.
37def hex(ws: List<&2, U32>) -> String:
38 Core.hex(ws)
39
40# Big-endian octets, represented as U32 values in 0..255.
41def digest_bytes(ws: List<&2, U32>) -> List<&2, U32>:
42 match ws:
43 case Nil{}:
44 Nil{}
45 case +w <> tail:
46 U32.and(U32.shrn(w, 24n), 255) <>
47 U32.and(U32.shrn(w, 16n), 255) <>
48 U32.and(U32.shrn(w, 8n), 255) <>
49 U32.and(w, 255) <> digest_bytes(tail)
50
51# Return the reference digest as eight big-endian octets per word.
52def sha256_bytes(bytes: List<&2, U32>) -> List<&2, U32>:
53 digest_bytes(sha256(bytes))
54
55# Compress one packed block using sixteen transient big-endian words. The file
56# buffer stays packed; no per-byte List is constructed for complete blocks.
57def packed.compress(words: List<&2, U32>, state: S.State) -> S.State:
58 match words:
59 case a <> b <> c <> d <> e <> f <> g <> h <> i <> j <> k <> l <> m <> n <> o <> p <> Nil{}:
60 Core.fips_compress16(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, 48n, state)
61 case _:
62 state
63
64# Read complete 64-byte blocks directly through a bounded package cursor.
65def packed.blocks(
66 remaining: Nat,
67 in_block: Nat,
68 pair: Packed.Cursor & Maybe<&2, U32>,
69 acc: List<&2, U32>,
70 state: S.State
71) -> Packed.Cursor & S.State:
72 match remaining:
73 case 0n:
74 match pair:
75 case (cursor, _):
76 (cursor, state)
77 case 1n:
78 match in_block:
79 case 0n:
80 match pair:
81 case (cursor, _):
82 (cursor, state)
83 case 1n:
84 match pair:
85 case (cursor, Some{word}):
86 (cursor, packed.compress(List.reverse(&2, U32, word <> acc), state))
87 case (cursor, None{}):
88 (cursor, state)
89 case 1n+more:
90 match pair:
91 case (cursor, _):
92 (cursor, state)
93 case 1n+more:
94 match in_block:
95 case 0n:
96 match pair:
97 case (cursor, _):
98 (cursor, state)
99 case 1n:
100 match pair:
101 case (cursor, Some{word}):
102 packed.blocks(more, 16n, Packed.Cursor.u32be(cursor), Nil{},
103 packed.compress(List.reverse(&2, U32, word <> acc), state))
104 case (cursor, None{}):
105 (cursor, state)
106 case 1n+left:
107 match pair:
108 case (cursor, Some{word}):
109 packed.blocks(more, left, Packed.Cursor.u32be(cursor), word <> acc, state)
110 case (cursor, None{}):
111 (cursor, state)
112
113# Read the remaining fewer than 64 bytes for the existing constant-space tail
114# padding routines. This list is bounded by 63 octets, regardless of file size.
115def packed.tail(
116 remaining: Nat,
117 pair: Packed.Cursor & Maybe<&2, U32>,
118 acc: List<&2, U32>
119) -> Packed.Cursor & List<&2, U32>:
120 match remaining:
121 case 0n:
122 match pair:
123 case (cursor, _):
124 (cursor, List.reverse(&2, U32, acc))
125 case 1n:
126 match pair:
127 case (cursor, Some{byte}):
128 (cursor, List.reverse(&2, U32, byte <> acc))
129 case (cursor, None{}):
130 (cursor, Nil{})
131 case 1n+more:
132 match pair:
133 case (cursor, Some{byte}):
134 packed.tail(more, Packed.Cursor.u8(cursor), byte <> acc)
135 case (cursor, None{}):
136 (cursor, Nil{})
137
138def packed.finish.tail(total: U32, pair: Packed.Cursor & List<&2, U32>, state: S.State) -> S.State:
139 match pair:
140 case (_, tail):
141 Core.finish_n(tail, U32.to_nat(total), 48n, state)
142
143def packed.final.tail(total: U32, tail_len: Nat, cursor: Packed.Cursor, state: S.State) -> S.State:
144 match tail_len:
145 case 0n:
146 packed.finish.tail(total, (cursor, Nil{}), state)
147 case 1n+more:
148 packed.finish.tail(total, packed.tail(1n+more, Packed.Cursor.u8(cursor), Nil{}), state)
149
150def packed.final(total: U32, tail_len: Nat, cursor: Packed.Cursor, state: S.State) -> List<&2, U32>:
151 Core.digest(packed.final.tail(total, tail_len, cursor, state))
152
153def packed.after_blocks(total: U32, tail_len: Nat, pair: Packed.Cursor & S.State) -> List<&2, U32>:
154 match pair:
155 case (cursor, state):
156 packed.final(total, tail_len, cursor, state)
157
158def packed.start.blocks(+total: U32, cursor: Packed.Cursor, blocks: Nat, +tail_len: Nat) -> List<&2, U32>:
159 match blocks:
160 case 0n:
161 packed.final(total, tail_len, cursor, Core.initial())
162 case 1n+more:
163 packed.after_blocks(total, tail_len,
164 packed.blocks(((1n+more) * 16n : Nat), 16n, Packed.Cursor.u32be(cursor), Nil{}, Core.initial()))
165
166def packed.start(+total: U32, cursor: Packed.Cursor) -> List<&2, U32>:
167 packed.start.blocks(total, cursor,
168 Nat.div(U32.to_nat(total), 64n), Nat.mod(U32.to_nat(total), 64n))
169
170# SHA-256 directly over packed bytes. Full blocks are read as sixteen u32be
171# words; only the final 0..63 octets use the existing tail padding interface.
172def sha256_packed(bytes: Packed.Bytes) -> List<&2, U32>:
173 match bytes:
174 case Packed.Bytes{+len, buf}:
175 packed.start(len, Packed.Cursor.new(Packed.Bytes{len, buf}))
176
177def packed.digest.finish.bytes(pair: Packed.Bytes & U32) -> Packed.Bytes:
178 match pair:
179 case (bytes, _):
180 bytes
181
182def packed.digest.finish(pair: Packed.Cursor & Bool) -> Packed.Bytes:
183 match pair:
184 case (cursor, _):
185 packed.digest.finish.bytes(Packed.Cursor.finish(cursor))
186
187def packed.digest.words(words: List<&2, U32>, pair: Packed.Cursor & Bool) -> Packed.Bytes:
188 match words:
189 case Nil{}:
190 packed.digest.finish(pair)
191 case word <> rest:
192 match pair:
193 case (cursor, _) :
194 packed.digest.words(rest, Packed.Cursor.put.u32be(cursor, word))
195
196# Return the SHA-256 digest as packed bytes in canonical big-endian order.
197def sha256_packed_bytes(bytes: Packed.Bytes) -> Packed.Bytes:
198 packed.digest.words(sha256_packed(bytes),
199 (Packed.Cursor.new(Packed.Bytes{32, Packed.alloc(32)}), True{}))