zorch.sha256_field_transcript¶
Field-element Transcript surface over the streaming SHA-256 core.
The SHA-256 sibling of the algebraic transcript.DuplexTranscript, and shaped
like it: a frozen pytree dataclass whose methods are plain traced state
transitions — jit-first, scan-threadable, no host substrate anywhere. It keeps
the Merlin byte framing (op tag, u64-LE count, SHA256(buffer ‖ ctr)
counter-squeeze, re-absorb) on the fixed-shape Sha256State
(hash/sha256.py), so a slice observe / sample is byte-identical to
ByteHashTranscript's observe_slice / sample_slice, and the proof-of-work
grind is grind/check_witness with DuplexTranscript's exact semantics
(device windowed search via zorch.grind; the advanced transcript absorbs the
witness regardless, and check_witness is the soundness gate).
Scheme-agnostic: dtype is the challenge element's (scalar) type. observe
bitcasts values to bytes and sample reinterprets squeezed bytes back, so the
element width is dtype.itemsize. A binary-field element wider than a scalar
dtype (e.g. a uint64[2] pair) rides the sumcheck only through a field-ops
seam the consumer supplies; a byte-framed challenger can use
ByteHashTranscript instead.
The observe/sample surface is exactly the Merlin wire's op vocabulary — one method per op tag, because the tag is transcript-semantic (two ops with the same payload and different tags produce different challenge streams), and a mode flag would be the same arity hidden in an argument:
observe(values) [OP_OBSERVE, KIND_SLICE] count-prefixed vector
observe_scalar(value) [OP_OBSERVE, KIND_SCALAR] per element, no prefix
observe_label(label) [OP_LABEL] domain separation
observe_bytes(data) [OP_BYTES] opaque bytes (roots, PoW)
sample(n) [OP_SQUEEZE, KIND_SLICE] count-prefixed squeeze
sample_scalar() [OP_SQUEEZE, KIND_SCALAR] one-element squeeze
Sha256FieldTranscript
dataclass
¶
Device SHA-256 transcript satisfying transcript.Transcript, threadable
through a lax.scan / @jit like DuplexTranscript. State is the
streaming Sha256State pytree; dtype (static) is the challenge element
type.
Source code in zorch/sha256_field_transcript.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | |
observe ¶
observe(values: Array) -> Sha256FieldTranscript
Absorb values under slice framing: [OP_OBSERVE, KIND_SLICE] ||
len8(count) || serialized bytes. Byte-identical to the byte
transcript's observe_slice of the same serialized bytes.
Source code in zorch/sha256_field_transcript.py
189 190 191 192 193 194 195 196 | |
observe_scalar ¶
observe_scalar(value: Array) -> Sha256FieldTranscript
Absorb under scalar framing [OP_OBSERVE, KIND_SCALAR] || elem_bytes
— no length prefix, a scalar's width being implicit in the dtype. A 0-d
value is one op; an [n] array is n ops (one per element, in order),
built as ONE absorb payload. Byte-identical to the byte transcript's
observe_scalar per element; distinct from observe (the KIND tag
differs).
Source code in zorch/sha256_field_transcript.py
198 199 200 201 202 203 204 205 | |
observe_scalar_and_sample ¶
observe_scalar_and_sample(
value: Array,
) -> tuple[Sha256FieldTranscript, Array]
observe_scalar then sample_scalar, as one marked region.
Source code in zorch/sha256_field_transcript.py
228 229 230 231 232 | |
observe_label ¶
observe_label(label: bytes) -> Sha256FieldTranscript
Absorb a domain-separation label [OP_LABEL] || len8(len) || label.
A compile-time host constant (labels are literals), so the whole absorb
is one constant payload. Byte-identical to the byte transcript.
Source code in zorch/sha256_field_transcript.py
234 235 236 237 238 239 240 | |
observe_bytes ¶
observe_bytes(data: Array) -> Sha256FieldTranscript
Absorb opaque bytes (e.g. a Merkle root computed on-device) under
[OP_BYTES] || len8(len) || data. data is a uint8 array whose length
is static (it rides the framing prefix). Byte-identical to the byte
transcript's observe_bytes of the same bytes.
Source code in zorch/sha256_field_transcript.py
242 243 244 245 246 247 248 249 | |
sample ¶
sample(n: int = 1) -> tuple[Sha256FieldTranscript, Array]
Squeeze n challenge elements: absorb [OP_SQUEEZE, KIND_SLICE] ||
len8(n), counter-squeeze n * itemsize bytes, re-absorb them, and
reinterpret to n elements of dtype.
Source code in zorch/sha256_field_transcript.py
251 252 253 254 255 256 257 258 259 | |
sample_scalar ¶
sample_scalar() -> tuple[Sha256FieldTranscript, Array]
Squeeze one challenge under scalar framing: absorb [OP_SQUEEZE,
KIND_SCALAR], counter-squeeze itemsize bytes, re-absorb, reinterpret
to one dtype element (0-D). Byte-identical to the byte transcript's
sample_scalar; distinct from sample(1) (the KIND tag differs).
Source code in zorch/sha256_field_transcript.py
261 262 263 264 265 266 267 268 | |
grind ¶
grind(
pow_bits: int, *, chunk: int = GRIND_WINDOW
) -> tuple[Sha256FieldTranscript, Array]
Find a proof-of-work witness — the lowest nonce whose
SHA256(state_digest || nonce_le8) has pow_bits leading zero bits —
and return the transcript advanced past it (the nonce absorbed under the
OP_BYTES wire), plus the witness. Fully traceable
(zorch.grind.grind_search windowed device search); does not raise on
an exhausted search: check_witness is the soundness gate, so which
witness the search returns is soundness-neutral.
Source code in zorch/sha256_field_transcript.py
302 303 304 305 306 307 308 309 310 311 312 313 | |
grind_and_sample ¶
grind_and_sample(
pow_bits: int, *, chunk: int = GRIND_WINDOW
) -> tuple[Sha256FieldTranscript, Array, Array]
Grind, then draw one scalar challenge, as ONE marked region — the
BLAKE3 row's grind_and_sample, on this wire.
Source code in zorch/sha256_field_transcript.py
337 338 339 340 341 342 343 344 | |
check_witness ¶
check_witness(
witness: Array, *, pow_bits: int
) -> tuple[Sha256FieldTranscript, Array]
Verifier mirror of grind: check the PoW (pow_bits == 0 requires
the canonical witness 0), then absorb the witness REGARDLESS so the
transcript stays in lockstep. Returns the advanced transcript and the
device boolean verdict.
Source code in zorch/sha256_field_transcript.py
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |