Skip to main content

binius_circuits/skein512/
mod.rs

1// Copyright 2025 Irreducible Inc.
2mod constants;
3#[cfg(test)]
4mod reference;
5
6use binius_core::word::Word;
7use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
8use constants::{C240, R512, TWEAK_TYPE_CFG, TWEAK_TYPE_MSG, TWEAK_TYPE_OUT};
9
10/// Skein512 circuit that processes a fixed number of 64-byte message blocks
11///
12/// This circuit implements the full Skein-512 hash function for fixed-length inputs,
13/// following the reference implementation. Unlike variable-length hash circuits,
14/// this expects exactly n_blocks of 64 bytes each as input.
15///
16/// # Circuit Structure
17///
18/// 1. **Configuration Block**: Process fixed config block to establish initial chaining value
19/// 2. **Message Blocks**: Process each of the n_blocks message blocks in sequence
20/// 3. **Output Block**: Generate final 512-bit digest
21///
22/// Each stage uses UBI (Unique Block Iteration) compression with appropriate tweak values.
23///
24/// # Arguments
25///
26/// * `n_blocks` - Number of 64-byte message blocks this circuit will process
27/// * `message` - Vector of message block wires, each containing 8 × 64-bit words
28/// * `digest` - Array of 8 wires representing the 512-bit output digest
29pub struct Skein512 {
30	/// Number of 64-byte blocks this circuit processes
31	pub n_blocks: usize,
32	/// Input message as blocks of 8 × 64-bit words each
33	pub message: Vec<[Wire; 8]>,
34	/// Expected 512-bit digest as 8 × 64-bit words
35	pub digest: [Wire; 8],
36}
37
38impl Skein512 {
39	pub fn new(builder: &CircuitBuilder, n_blocks: usize) -> Self {
40		assert!(n_blocks > 0, "n_blocks must be positive");
41
42		// Create message block wires - each block is 8 × 64-bit words
43		let message: Vec<[Wire; 8]> = (0..n_blocks)
44			.map(|_| std::array::from_fn(|_| builder.add_witness()))
45			.collect();
46
47		// Create digest wires - 8 × 64-bit words for 512-bit output
48		let digest: [Wire; 8] = std::array::from_fn(|_| builder.add_witness());
49
50		// Build the circuit following Skein-512 algorithm
51		Self::build_circuit(builder, &message, digest, n_blocks);
52
53		Self {
54			n_blocks,
55			message,
56			digest,
57		}
58	}
59
60	fn build_circuit(
61		builder: &CircuitBuilder,
62		message: &[[Wire; 8]],
63		digest: [Wire; 8],
64		n_blocks: usize,
65	) {
66		// ---- Stage 1: Configuration UBI Block ----
67		//
68		// Process the fixed Skein-512 configuration block to establish initial chaining value.
69		// Config block contains: "SHA3", version, output length (512 bits), etc.
70
71		// Build 64-byte configuration block (same as reference implementation)
72		let mut cfg = [0u8; 64];
73		cfg[0..4].copy_from_slice(&0x3341_4853u32.to_le_bytes()); // "SHA3"
74		cfg[4..6].copy_from_slice(&1u16.to_le_bytes()); // version 1
75		cfg[6..8].copy_from_slice(&0u16.to_le_bytes()); // reserved
76		cfg[8..16].copy_from_slice(&512u64.to_le_bytes()); // output length in bits
77		cfg[16] = 0; // Yl
78		cfg[17] = 0; // Yf  
79		cfg[18] = 0; // Ym
80		// rest already zero (bytes 19..31 and 32..63)
81
82		// Convert config bytes to 8 × 64-bit words
83		let cfg_words: [Wire; 8] = std::array::from_fn(|i| {
84			let mut word_bytes = [0u8; 8];
85			word_bytes.copy_from_slice(&cfg[i * 8..(i + 1) * 8]);
86			let word_val = u64::from_le_bytes(word_bytes);
87			builder.add_constant_64(word_val)
88		});
89
90		// Create configuration tweak: T_TYPE_CFG, position=32, FIRST|FINAL
91		let pos_lo = builder.add_constant_64(32);
92		let pos_hi = builder.add_constant_64(0);
93		let (t_low, t_high) = tweak(builder, pos_lo, pos_hi, true, true, TWEAK_TYPE_CFG);
94		let t_cfg_wires = [t_low, t_high];
95
96		// Initial chaining value is zero for Skein config UBI
97		let cv0: [Wire; 8] = std::array::from_fn(|_| builder.add_constant_64(0));
98
99		// Config UBI: CV1 = UBI(CV0=0, T_cfg, config_block)
100		let config_ubi_out = ubi_block(builder, cv0, t_cfg_wires, cfg_words);
101
102		// ---- Stage 2: Message UBI Blocks ----
103		//
104		// Process each message block with appropriate tweak values
105
106		let mut current_cv = config_ubi_out;
107
108		// Process each message block
109		for block_idx in 0..n_blocks {
110			// Calculate position: number of bytes processed so far INCLUDING this block
111			let pos_end = ((block_idx + 1) * 64) as u64;
112
113			// Tweak flags - for the reference semantics, no message block is final except the empty
114			// one
115			let is_first = block_idx == 0;
116			let is_final = false; // Message blocks are never final in reference implementation
117
118			// Create message tweak: T_TYPE_MSG, position, first/final flags
119			let pos_lo = builder.add_constant_64(pos_end);
120			let pos_hi = builder.add_constant_64(0); // high 32 bits of position (we assume < 4GB messages)
121			let (t_low, t_high) =
122				tweak(builder, pos_lo, pos_hi, is_first, is_final, TWEAK_TYPE_MSG);
123			let t_msg_wires = [t_low, t_high];
124
125			// Message UBI: CV_{i+1} = UBI(CV_i, T_msg, message_block_i)
126			current_cv = ubi_block(builder, current_cv, t_msg_wires, message[block_idx]);
127		}
128
129		// ---- Final Message Block (Empty) ----
130		//
131		// According to reference implementation, we always need a final empty message block
132		// when the message length is a multiple of 64 bytes
133
134		// Calculate final position (total message length)
135		let final_pos = (n_blocks * 64) as u64;
136
137		// Create empty final block (all zeros)
138		let empty_block: [Wire; 8] = std::array::from_fn(|_| builder.add_constant_64(0));
139
140		// Create final message tweak: first=false, final=true
141		let pos_lo_final = builder.add_constant_64(final_pos);
142		let pos_hi_final = builder.add_constant_64(0);
143		let (t_low, t_high) = tweak(
144			builder,
145			pos_lo_final,
146			pos_hi_final,
147			false,
148			true, // first=false, final=true
149			TWEAK_TYPE_MSG,
150		);
151		let t_msg_final_wires = [t_low, t_high];
152
153		// Final message UBI with empty block
154		current_cv = ubi_block(builder, current_cv, t_msg_final_wires, empty_block);
155
156		// ---- Stage 3: Output UBI Block ----
157		//
158		// Generate final digest by processing counter block
159
160		// Output block: 8 bytes counter (0) + 56 bytes zero padding
161		// Match reference implementation: bytes first, then convert to words
162		let mut out_bytes = [0u8; 64];
163		out_bytes[0..8].copy_from_slice(&0u64.to_le_bytes()); // counter=0 in first 8 bytes, little-endian
164		// rest already zero (bytes 8..63)
165
166		// Convert to words using the same byte-to-word conversion as reference
167		let out_block: [Wire; 8] = std::array::from_fn(|i| {
168			let mut word_bytes = [0u8; 8];
169			word_bytes.copy_from_slice(&out_bytes[i * 8..(i + 1) * 8]);
170			let word_val = u64::from_le_bytes(word_bytes);
171			builder.add_constant_64(word_val)
172		});
173
174		// Create output tweak: T_TYPE_OUT, position=8 (counter size), FIRST|FINAL
175		let pos_lo = builder.add_constant_64(8);
176		let pos_hi = builder.add_constant_64(0);
177		let (t_low, t_high) = tweak(builder, pos_lo, pos_hi, true, true, TWEAK_TYPE_OUT);
178		let t_out_wires = [t_low, t_high];
179
180		// Output UBI: final_digest = UBI(CV_final, T_out, output_block)
181		let computed_digest = ubi_block(builder, current_cv, t_out_wires, out_block);
182
183		// ---- Stage 4: Digest Verification ----
184		//
185		// Verify that computed digest matches expected digest
186
187		builder.assert_eq_v("skein512_digest", computed_digest, digest);
188	}
189
190	/// Populate the message wires with input message blocks
191	pub fn populate_message(&self, w: &mut WitnessFiller<'_>, message_blocks: &[[u8; 64]]) {
192		assert_eq!(
193			message_blocks.len(),
194			self.n_blocks,
195			"Message blocks length {} != expected {}",
196			message_blocks.len(),
197			self.n_blocks
198		);
199
200		// Pack each 64-byte block into 8 × 64-bit words (little-endian)
201		for (block_idx, block_bytes) in message_blocks.iter().enumerate() {
202			for (word_idx, word_bytes) in block_bytes.chunks(8).enumerate() {
203				let mut padded_bytes = [0u8; 8];
204				padded_bytes[..word_bytes.len()].copy_from_slice(word_bytes);
205				let word_val = u64::from_le_bytes(padded_bytes);
206				w[self.message[block_idx][word_idx]] = Word(word_val);
207			}
208		}
209	}
210
211	/// Populate the digest wire with expected hash output
212	pub fn populate_digest(&self, w: &mut WitnessFiller<'_>, expected_digest: [u8; 64]) {
213		// Pack 64 bytes into 8 × 64-bit words (little-endian)
214		for (i, word_bytes) in expected_digest.chunks(8).enumerate() {
215			let word_val = u64::from_le_bytes(word_bytes.try_into().unwrap());
216			w[self.digest[i]] = Word(word_val);
217		}
218	}
219
220	/// Get the digest wires
221	pub const fn digest_wires(&self) -> [Wire; 8] {
222		self.digest
223	}
224
225	/// Get the message block wires
226	pub fn message_wires(&self) -> &[[Wire; 8]] {
227		&self.message
228	}
229}
230
231/// Mix component for Skein-512 hash function
232///
233/// Performs the Threefish MIX operation, which is the core building block of Threefish rounds.
234///
235/// The MIX algorithm is:
236/// - a' = a + b (64-bit addition)
237/// - b' = ROTL(b, r) ^ a' (rotate left b by r bits, then XOR with new a)
238///
239/// Where:
240/// - a, b: input 64-bit words
241/// - r: rotation amount (compile-time constant, 0-63)
242/// - a', b': output 64-bit words
243///
244/// This operation provides both diffusion (through rotation) and confusion (through XOR)
245/// while maintaining the avalanche property essential for cryptographic security.
246fn mix(circuit: &CircuitBuilder, a: Wire, b: Wire, r: u32) -> (Wire, Wire) {
247	// a' = a + b (64-bit addition, ignoring carry)
248	let (a_out, _) = circuit.iadd(a, b);
249
250	// b' = ROTL(b, r) ^ a'
251	let b_rotated = circuit.rotl(b, r);
252	let b_out = circuit.bxor(b_rotated, a_out);
253
254	(a_out, b_out)
255}
256
257/// Permute512 component for Skein-512 hash function
258///
259/// Performs the Threefish-512 word permutation used between rounds.
260///
261/// The permutation algorithm is a simple rearrangement of 8 words:
262/// - output\[i\] = input\[π\[i\]\] where π = \[2, 1, 4, 7, 6, 5, 0, 3\]
263///
264/// This corresponds to the Threefish permutation from Table 3 of the Skein specification
265/// for Nw=8 (8 words). The permutation is applied after the MIX operations in each round
266/// to ensure proper diffusion across the state.
267const fn permute_512(_circuit: &CircuitBuilder, x: [Wire; 8]) -> [Wire; 8] {
268	[x[2], x[1], x[4], x[7], x[6], x[5], x[0], x[3]]
269}
270
271/// ThreefishRound component for Skein-512 hash function
272///
273/// Performs one round of the Threefish-512 block cipher, which consists of:
274/// 1. Four parallel MIX operations on word pairs (0,1), (2,3), (4,5), (6,7)
275/// 2. Word permutation to ensure proper diffusion
276///
277/// Each round uses round-specific rotation constants from the R512 table.
278/// Threefish-512 uses 72 rounds total, with the rotation pattern repeating every 8 rounds.
279///
280/// The round algorithm:
281/// - Apply MIX(v\[0\], v\[1\], r\[0\]) → (v0', v1')
282/// - Apply MIX(v\[2\], v\[3\], r\[1\]) → (v2', v3')
283/// - Apply MIX(v\[4\], v\[5\], r\[2\]) → (v4', v5')
284/// - Apply MIX(v\[6\], v\[7\], r\[3\]) → (v6', v7')
285/// - Apply permutation π to [v0', v1', v2', v3', v4', v5', v6', v7']
286fn threefish_round(circuit: &CircuitBuilder, v_in: [Wire; 8], round_idx: usize) -> [Wire; 8] {
287	// Get rotation constants for this round (pattern repeats every 8 rounds)
288	let r = R512[round_idx % 8];
289
290	// Apply 4 parallel MIX operations on word pairs
291	// MIX pairs for 512-bit: (0,1), (2,3), (4,5), (6,7)
292	let (mix0_a, mix0_b) = mix(circuit, v_in[0], v_in[1], r[0]);
293	let (mix1_a, mix1_b) = mix(circuit, v_in[2], v_in[3], r[1]);
294	let (mix2_a, mix2_b) = mix(circuit, v_in[4], v_in[5], r[2]);
295	let (mix3_a, mix3_b) = mix(circuit, v_in[6], v_in[7], r[3]);
296
297	// Reassemble state from MIX outputs
298	let mixed_state = [
299		mix0_a, mix0_b, // (v0, v1) from MIX(v[0], v[1])
300		mix1_a, mix1_b, // (v2, v3) from MIX(v[2], v[3])
301		mix2_a, mix2_b, // (v4, v5) from MIX(v[4], v[5])
302		mix3_a, mix3_b, // (v6, v7) from MIX(v[6], v[7])
303	];
304
305	// Apply permutation to complete the round
306	permute_512(circuit, mixed_state)
307}
308
309/// ThreefishSubkey component for Skein-512 hash function
310///
311/// Generates subkeys for the Threefish-512 block cipher rounds.
312///
313/// The subkey generation algorithm is:
314/// - sk\[i\] = k\[(s + i) % 9\] for i in 0..8 (base subkey from extended key)
315/// - sk\[5\] += t\[s % 3\] (add tweak component)
316/// - sk\[6\] += t\[(s + 1) % 3\] (add next tweak component)
317/// - sk\[7\] += s (add round number)
318///
319/// Where:
320/// - s: subkey/round index (0-18 for 72-round Threefish-512)
321/// - k: extended key array [k0, k1, ..., k7, k8] where k8 = C240 ^ (k0 ^ k1 ^ ... ^ k7)
322/// - t: extended tweak array [t0, t1, t2] where t2 = t0 ^ t1
323fn threefish_subkey(circuit: &CircuitBuilder, s: usize, k: [Wire; 9], t: [Wire; 3]) -> [Wire; 8] {
324	// Create base subkey by rotating through extended key
325	let mut subkey = std::array::from_fn(|i| k[(s + i) % 9]);
326
327	// Add tweak components to specific positions
328	// sk[5] += t[s % 3] (64-bit addition, ignoring carry)
329	let (sum5, _) = circuit.iadd(subkey[5], t[s % 3]);
330	subkey[5] = sum5;
331
332	// sk[6] += t[(s + 1) % 3] (64-bit addition, ignoring carry)
333	let (sum6, _) = circuit.iadd(subkey[6], t[(s + 1) % 3]);
334	subkey[6] = sum6;
335
336	// sk[7] += s (add round number as constant)
337	let round_constant = circuit.add_constant_64(s as u64);
338	let (sum7, _) = circuit.iadd(subkey[7], round_constant);
339	subkey[7] = sum7;
340
341	subkey
342}
343
344/// Tweak component for Skein-512 hash function
345///
346/// The tweak is a 128-bit value used in the Threefish block cipher that underlies Skein.
347/// It encodes position information, type flags, and first/final block indicators.
348///
349/// According to the Skein specification, the tweak bit layout is:
350/// - Bits 0-95: Position (number of bytes processed so far, including this block)
351/// - Bits 96-111: Reserved (must be zero)
352/// - Bits 112-118: Tree level (0 for sequential hashing)
353/// - Bit 119: Bit pad flag (0 for byte-aligned messages)
354/// - Bits 120-125: Type field (CFG=4, MSG=48, OUT=63)
355/// - Bit 126: First block flag
356/// - Bit 127: Final block flag
357fn tweak(
358	circuit: &CircuitBuilder,
359	pos_bytes_lo: Wire,
360	mut pos_bytes_hi: Wire,
361	is_first: bool,
362	is_final: bool,
363	cfg: u64,
364) -> (Wire, Wire) {
365	let low_bytes_mask = circuit.add_constant_64(u32::MAX as u64);
366	pos_bytes_hi = circuit.band(pos_bytes_hi, low_bytes_mask);
367
368	let t_low = pos_bytes_lo;
369	// The high word is assembled from bit ranges that do not overlap, so each OR carries nothing
370	// and lowers to a free XOR instead of an AND:
371	//   - `pos_bytes_hi` is masked to bits 0..32, and the type field `cfg << 56` sits in bits
372	//     56..62 (the tweak type field is bits 120..125, i.e. cfg < 64), so the two never share a
373	//     bit;
374	//   - the first and final flags own bits 62 and 63, which the type field leaves clear.
375	let mut t_high = circuit.bxor(pos_bytes_hi, circuit.add_constant_64(cfg << 56));
376
377	if is_first {
378		t_high = circuit.bxor(t_high, circuit.add_constant_64(1 << 62));
379	}
380
381	if is_final {
382		t_high = circuit.bxor(t_high, circuit.add_constant_64(1 << 63));
383	}
384
385	(t_low, t_high)
386}
387
388/// UbiBlock component for Skein-512 hash function
389///
390/// Implements the UBI (Unique Block Iteration) compression for Skein-512.
391///
392/// Inputs:
393/// - chaining_value: [Wire; 8] (input chaining value)
394/// - tweak: [Wire; 2] (tweak words)
395/// - block: [Wire; 8] (message block)
396///
397/// Outputs:
398/// - g_out: [Wire; 8] (output chaining value)
399fn ubi_block(
400	circuit: &CircuitBuilder,
401	chaining_value: [Wire; 8],
402	tweak: [Wire; 2],
403	block: [Wire; 8],
404) -> [Wire; 8] {
405	// G' = Threefish(K = CV, T = tweak, M = block) XOR M
406	let out = Threefish512Block::new(circuit, chaining_value, tweak, block).v_out;
407	std::array::from_fn(|i| circuit.bxor(out[i], block[i]))
408}
409
410/// Threefish4RoundsWithInjection component for Skein-512 hash function
411///
412/// Performs a group of 4 Threefish-512 rounds with subkey injection before the group.
413/// This matches the reference implementation's `threefish_4rounds_with_injection`.
414///
415/// - Inject subkey (computed from extended key and tweak) before the 4 rounds
416/// - Run 4 consecutive Threefish rounds (using round indices base..base+3)
417///
418/// Inputs:
419/// - v_in: [Wire; 8] (input state)
420/// - k: [Wire; 9] (extended key)
421/// - t: [Wire; 3] (extended tweak)
422/// - group_idx: usize (group index, 0..=18 for 72 rounds)
423///
424/// Outputs:
425/// - v_out: [Wire; 8] (output state after 4 rounds and subkey injection)
426struct Threefish4RoundsWithInjection {
427	v_out: [Wire; 8],
428}
429
430impl Threefish4RoundsWithInjection {
431	fn new(
432		circuit: &CircuitBuilder,
433		v_in: [Wire; 8],
434		k: [Wire; 9],
435		t: [Wire; 3],
436		group_idx: usize,
437	) -> Self {
438		// Inject subkey before the 4 rounds
439		let subkey = threefish_subkey(circuit, group_idx, k, t);
440		let mut v_out = std::array::from_fn(|i| {
441			let (sum, _) = circuit.iadd(v_in[i], subkey[i]);
442			sum
443		});
444
445		// Do 4 rounds
446		let base = group_idx * 4;
447		for round in 0..4 {
448			v_out = threefish_round(circuit, v_out, base + round);
449		}
450
451		Self { v_out }
452	}
453}
454
455/// Threefish512Block component for Skein-512 hash function
456///
457/// Implements the full Threefish-512 block function as in the reference implementation.
458///
459/// Inputs:
460/// - key: [Wire; 8] (key words)
461/// - tweak: [Wire; 2] (tweak words)
462/// - block: [Wire; 8] (plaintext/message block)
463///
464/// Outputs:
465/// - v_out: [Wire; 8] (ciphertext/output block)
466struct Threefish512Block {
467	v_out: [Wire; 8],
468}
469
470impl Threefish512Block {
471	fn new(circuit: &CircuitBuilder, key: [Wire; 8], tweak: [Wire; 2], block: [Wire; 8]) -> Self {
472		// Expand key to 9 words: k[8] = C240 ^ (k0 ^ ... ^ k7)
473		let c240 = circuit.add_constant_64(C240);
474		let mut k_vec = Vec::with_capacity(9);
475		let mut sum = key[0];
476		k_vec.push(key[0]);
477		for i in 1..8 {
478			k_vec.push(key[i]);
479			sum = circuit.bxor(sum, key[i]);
480		}
481		k_vec.push(circuit.bxor(c240, sum));
482		let k: [Wire; 9] = k_vec.try_into().expect("Vec to array conversion");
483
484		// Expand tweak to 3 words: t2 = t0 ^ t1
485		let t0 = tweak[0];
486		let t1 = tweak[1];
487		let t2 = circuit.bxor(t0, t1);
488		let t = [t0, t1, t2];
489
490		// Initial state is the plaintext/message block
491		let mut v = block;
492
493		// 72 rounds = 18 groups of 4 rounds, with subkey injections
494		for g in 0..18 {
495			let group = Threefish4RoundsWithInjection::new(circuit, v, k, t, g);
496			v = group.v_out;
497		}
498
499		// Final subkey injection (18th injection after the 72 rounds)
500		let subkey = threefish_subkey(circuit, 18, k, t);
501		let v_out = std::array::from_fn(|i| {
502			let (sum, _) = circuit.iadd(v[i], subkey[i]);
503			sum
504		});
505
506		Self { v_out }
507	}
508}
509
510#[cfg(test)]
511mod tests {
512	use binius_frontend::CircuitBuilder;
513
514	use super::*;
515	use crate::skein512::reference;
516
517	// Tests from skein512.rs
518	fn test_skein512_with_blocks(message_blocks: &[[u8; 64]]) {
519		let n_blocks = message_blocks.len();
520		let builder = CircuitBuilder::new();
521
522		let skein = Skein512::new(&builder, n_blocks);
523		let circuit = builder.build();
524
525		let expected_digest = reference::skein512(message_blocks.as_flattened());
526
527		let mut w = circuit.new_witness_filler();
528		skein.populate_message(&mut w, message_blocks);
529		skein.populate_digest(&mut w, expected_digest);
530
531		circuit.populate_wire_witness(&mut w).unwrap();
532		let cs = circuit.constraint_system();
533		cs.verify(&w.into_value_vec()).unwrap();
534	}
535
536	#[test]
537	fn test_skein512_single_block() {
538		let mut block = [0u8; 64];
539		block[0..3].copy_from_slice(b"abc");
540
541		test_skein512_with_blocks(&[block]);
542	}
543
544	#[test]
545	fn test_skein512_64_byte_zeros() {
546		test_skein512_with_blocks(&[[0u8; 64]]);
547	}
548
549	#[test]
550	fn test_skein512_two_blocks() {
551		let mut block1 = [0xAAu8; 64];
552		let mut block2 = [0x55u8; 64];
553
554		block1[0] = 0x01;
555		block2[63] = 0xFF;
556
557		test_skein512_with_blocks(&[block1, block2]);
558	}
559
560	#[test]
561	fn test_skein512_multiple_blocks() {
562		// Test with 4 blocks of different patterns
563		let blocks = [
564			[0x00u8; 64], // all zeros
565			[0xFFu8; 64], // all ones
566			{
567				let mut block = [0u8; 64];
568				for (i, byte) in block.iter_mut().enumerate() {
569					*byte = (i % 256) as u8;
570				}
571				block
572			},
573			{
574				let mut block = [0u8; 64];
575				block[0..26].copy_from_slice(b"abcdefghijklmnopqrstuvwxyz");
576				block
577			},
578		];
579
580		test_skein512_with_blocks(&blocks);
581	}
582
583	// Tests from mix.rs
584	#[test]
585	fn test_mix_correctness() {
586		let test_cases = [
587			// (a, b, r, description)
588			(0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 0, "Zero rotation"),
589			(0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 1, "Single bit rotation"),
590			(0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 8, "Byte rotation"),
591			(0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 32, "Half-word rotation"),
592			(0u64, 0u64, 0, "Both zero, no rotation"),
593			(u64::MAX, u64::MAX, 0, "Both max, no rotation"),
594			(0u64, u64::MAX, 32, "Zero and max"),
595			(u64::MAX, 0u64, 32, "Max and zero"),
596			(0x8000000000000000u64, 0x0000000000000001u64, 1, "MSB and LSB"),
597			(0x0000000000000001u64, 0x8000000000000000u64, 16, "LSB and MSB with rotation"),
598		];
599
600		for (a_val, b_val, r, description) in test_cases {
601			let (expected_a, expected_b) = reference::mix(a_val, b_val, r);
602
603			let circuit = CircuitBuilder::new();
604
605			let a_wire = circuit.add_witness();
606			let b_wire = circuit.add_witness();
607
608			let (a_out, b_out) = mix(&circuit, a_wire, b_wire, r);
609
610			let expected_a_wire = circuit.add_constant(Word(expected_a));
611			let expected_b_wire = circuit.add_constant(Word(expected_b));
612
613			circuit.assert_eq(format!("{}_a_out", description), a_out, expected_a_wire);
614			circuit.assert_eq(format!("{}_b_out", description), b_out, expected_b_wire);
615
616			let built_circuit = circuit.build();
617			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
618
619			let mut witness = built_circuit.new_witness_filler();
620
621			witness[a_wire] = Word(a_val);
622			witness[b_wire] = Word(b_val);
623
624			built_circuit.populate_wire_witness(&mut witness).unwrap();
625
626			let cs = built_circuit.constraint_system();
627
628			cs.verify(&witness.into_value_vec())
629				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
630		}
631	}
632
633	// Tests from permute_512.rs
634	#[test]
635	fn test_permute512_correctness() {
636		let test_cases = [
637			// (input_vals, description)
638			(
639				[
640					0x0123456789ABCDEFu64,
641					0xFEDCBA9876543210u64,
642					0x1111111111111111u64,
643					0x2222222222222222u64,
644					0x3333333333333333u64,
645					0x4444444444444444u64,
646					0x5555555555555555u64,
647					0x6666666666666666u64,
648				],
649				"Sequential pattern",
650			),
651			([0xAAAAAAAAAAAAAAAAu64; 8], "All same values"),
652			([0u64, 1u64, 2u64, 3u64, 4u64, 5u64, 6u64, 7u64], "Simple incremental"),
653			(
654				[
655					u64::MAX,
656					0u64,
657					u64::MAX,
658					0u64,
659					u64::MAX,
660					0u64,
661					u64::MAX,
662					0u64,
663				],
664				"Alternating pattern",
665			),
666		];
667
668		for (input_vals, description) in test_cases {
669			let expected = reference::permute_512(input_vals);
670
671			let circuit = CircuitBuilder::new();
672
673			let input_wires = std::array::from_fn(|_| circuit.add_witness());
674
675			let permuted = permute_512(&circuit, input_wires);
676
677			for (i, &expected_val) in expected.iter().enumerate() {
678				let expected_wire = circuit.add_constant(Word(expected_val));
679				circuit.assert_eq(format!("{}[{}]", description, i), permuted[i], expected_wire);
680			}
681
682			let built_circuit = circuit.build();
683
684			let mut witness = built_circuit.new_witness_filler();
685
686			for (i, &val) in input_vals.iter().enumerate() {
687				witness[input_wires[i]] = Word(val);
688			}
689
690			built_circuit.populate_wire_witness(&mut witness).unwrap();
691
692			let cs = built_circuit.constraint_system();
693
694			cs.verify(&witness.into_value_vec())
695				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
696		}
697	}
698
699	// Tests from threefish_round.rs
700	#[test]
701	fn test_threefish_round_correctness() {
702		// Test various round indices with different input states
703		let test_cases = [
704			// (round_idx, input_state, description)
705			(
706				0,
707				[
708					0x0123456789ABCDEFu64,
709					0xFEDCBA9876543210u64,
710					0x1111111111111111u64,
711					0x2222222222222222u64,
712					0x3333333333333333u64,
713					0x4444444444444444u64,
714					0x5555555555555555u64,
715					0x6666666666666666u64,
716				],
717				"Round 0 with sequential pattern",
718			),
719			(
720				1,
721				[
722					0x0123456789ABCDEFu64,
723					0xFEDCBA9876543210u64,
724					0x1111111111111111u64,
725					0x2222222222222222u64,
726					0x3333333333333333u64,
727					0x4444444444444444u64,
728					0x5555555555555555u64,
729					0x6666666666666666u64,
730				],
731				"Round 1 with sequential pattern",
732			),
733			(
734				7,
735				[
736					0x0123456789ABCDEFu64,
737					0xFEDCBA9876543210u64,
738					0x1111111111111111u64,
739					0x2222222222222222u64,
740					0x3333333333333333u64,
741					0x4444444444444444u64,
742					0x5555555555555555u64,
743					0x6666666666666666u64,
744				],
745				"Round 7 with sequential pattern",
746			),
747			(
748				8,
749				[
750					0x0123456789ABCDEFu64,
751					0xFEDCBA9876543210u64,
752					0x1111111111111111u64,
753					0x2222222222222222u64,
754					0x3333333333333333u64,
755					0x4444444444444444u64,
756					0x5555555555555555u64,
757					0x6666666666666666u64,
758				],
759				"Round 8 (same as round 0)",
760			),
761		];
762
763		for (round_idx, input_state, description) in test_cases {
764			let expected = reference::threefish_round(input_state, round_idx);
765
766			let circuit = CircuitBuilder::new();
767
768			let input_wires = std::array::from_fn(|_| circuit.add_witness());
769
770			let v_out = threefish_round(&circuit, input_wires, round_idx);
771
772			for i in 0..8 {
773				let expected_wire = circuit.add_constant(Word(expected[i]));
774				circuit.assert_eq(format!("{}[{}]", description, i), v_out[i], expected_wire);
775			}
776
777			let built_circuit = circuit.build();
778			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
779
780			let mut witness = built_circuit.new_witness_filler();
781
782			for (i, &val) in input_state.iter().enumerate() {
783				witness[input_wires[i]] = Word(val);
784			}
785
786			built_circuit.populate_wire_witness(&mut witness).unwrap();
787
788			let cs = built_circuit.constraint_system();
789
790			cs.verify(&witness.into_value_vec())
791				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
792		}
793	}
794
795	// Tests from threefish_subkey.rs
796	#[test]
797	fn test_threefish_subkey_correctness() {
798		let test_cases = [
799			// (s, description)
800			(0, "First subkey (s=0)"),
801			(1, "Second subkey (s=1)"),
802			(5, "Middle subkey (s=5)"),
803			(9, "Wrap-around subkey (s=9)"),
804			(18, "Final subkey (s=18)"),
805		];
806
807		for (s, description) in test_cases {
808			let k_vals = [
809				0x0123456789ABCDEFu64,
810				0xFEDCBA9876543210u64,
811				0x1111111111111111u64,
812				0x2222222222222222u64,
813				0x3333333333333333u64,
814				0x4444444444444444u64,
815				0x5555555555555555u64,
816				0x6666666666666666u64,
817				0x7777777777777777u64, // k8 (extended key)
818			];
819
820			let t_vals = [
821				0xAAAAAAAAAAAAAAAAu64,
822				0xBBBBBBBBBBBBBBBBu64,
823				0x1111111111111111u64, // t2 = t0 ^ t1
824			];
825
826			let expected = reference::threefish_subkey(s, k_vals, t_vals);
827
828			let circuit = CircuitBuilder::new();
829
830			let k_wires = std::array::from_fn(|_| circuit.add_witness());
831			let t_wires = std::array::from_fn(|_| circuit.add_witness());
832
833			let subkey = threefish_subkey(&circuit, s, k_wires, t_wires);
834
835			for (i, &expected_val) in expected.iter().enumerate() {
836				let expected_wire = circuit.add_constant(Word(expected_val));
837				circuit.assert_eq(format!("{}[{}]", description, i), subkey[i], expected_wire);
838			}
839
840			let built_circuit = circuit.build();
841			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
842
843			let mut witness = built_circuit.new_witness_filler();
844
845			for (i, &val) in k_vals.iter().enumerate() {
846				witness[k_wires[i]] = Word(val);
847			}
848			for (i, &val) in t_vals.iter().enumerate() {
849				witness[t_wires[i]] = Word(val);
850			}
851
852			built_circuit.populate_wire_witness(&mut witness).unwrap();
853
854			let cs = built_circuit.constraint_system();
855
856			cs.verify(&witness.into_value_vec())
857				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
858		}
859	}
860
861	#[test]
862	fn test_threefish_subkey_edge_cases() {
863		let test_cases = [
864			// (k_pattern, t_pattern, description)
865			([0u64; 9], [0u64; 3], "All zeros"),
866			([u64::MAX; 9], [u64::MAX; 3], "All ones"),
867			([0xDEADBEEFCAFEBABEu64; 9], [0x0123456789ABCDEFu64; 3], "Mixed pattern"),
868		];
869
870		for (k_vals, t_vals, description) in test_cases {
871			let circuit = CircuitBuilder::new();
872
873			let k_wires = std::array::from_fn(|_| circuit.add_witness());
874			let t_wires = std::array::from_fn(|_| circuit.add_witness());
875
876			let s = 9;
877			let subkey = threefish_subkey(&circuit, s, k_wires, t_wires);
878			let expected = reference::threefish_subkey(s, k_vals, t_vals);
879
880			for i in 0..8 {
881				circuit.assert_eq(
882					format!("{}[{}]", description, i),
883					subkey[i],
884					circuit.add_constant(Word(expected[i])),
885				);
886			}
887
888			let built_circuit = circuit.build();
889			let mut witness = built_circuit.new_witness_filler();
890
891			for (i, &val) in k_vals.iter().enumerate() {
892				witness[k_wires[i]] = Word(val);
893			}
894			for (i, &val) in t_vals.iter().enumerate() {
895				witness[t_wires[i]] = Word(val);
896			}
897
898			built_circuit.populate_wire_witness(&mut witness).unwrap();
899
900			let cs = built_circuit.constraint_system();
901			cs.verify(&witness.into_value_vec())
902				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
903		}
904	}
905
906	// Tests from tweak.rs
907	#[test]
908	fn test_tweak_correctness() {
909		let test_cases = [
910			// (position, type_code, is_first, is_final, description)
911			(0u128, false, false, "CFG: neither first nor final"),
912			(0u128, true, false, "CFG: first only"),
913			(0u128, false, true, "CFG: final only"),
914			(0u128, true, true, "CFG: both first and final"),
915			(64u128, false, false, "MSG 64 bytes: neither"),
916			(64u128, true, false, "MSG 64 bytes: first only"),
917			(64u128, false, true, "MSG 64 bytes: final only"),
918			(64u128, true, true, "MSG 64 bytes: both"),
919			(128u128, false, false, "OUT 128 bytes: neither"),
920			(128u128, true, false, "OUT 128 bytes: first only"),
921			(128u128, false, true, "OUT 128 bytes: final only"),
922			(128u128, true, true, "OUT 128 bytes: both"),
923			// Test with larger position values
924			(0x123456789ABCDEu128, true, false, "MSG large pos: first only"),
925			(0x123456789ABCDEu128, false, true, "MSG large pos: final only"),
926		];
927
928		fn test_tweak_inner(
929			pos_bytes: u128,
930			is_first: bool,
931			is_final: bool,
932			description: &str,
933			cfg: u64,
934		) {
935			let expected = reference::tweak(
936				cfg,
937				pos_bytes as u64,
938				(pos_bytes >> 64) as u64,
939				is_first,
940				is_final,
941			);
942
943			let circuit = CircuitBuilder::new();
944			let pos_t0 = circuit.add_witness();
945			let pos_t1 = circuit.add_witness();
946
947			let (t_low, t_high) = tweak(&circuit, pos_t0, pos_t1, is_first, is_final, cfg);
948
949			let expected_t0 = circuit.add_constant(Word(expected[0]));
950			let expected_t1 = circuit.add_constant(Word(expected[1]));
951
952			circuit.assert_eq(format!("{}_t_low", description), t_low, expected_t0);
953			circuit.assert_eq(format!("{}_t_high", description), t_high, expected_t1);
954
955			let built_circuit = circuit.build();
956			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
957
958			let mut witness = built_circuit.new_witness_filler();
959
960			witness[pos_t0] = Word(pos_bytes as u64);
961			witness[pos_t1] = Word((pos_bytes >> 64) as u64);
962
963			built_circuit.populate_wire_witness(&mut witness).unwrap();
964
965			let cs = built_circuit.constraint_system();
966
967			cs.verify(&witness.into_value_vec())
968				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
969		}
970
971		for (pos_bytes, is_first, is_final, description) in test_cases {
972			test_tweak_inner(pos_bytes, is_first, is_final, description, TWEAK_TYPE_OUT);
973		}
974	}
975
976	// Tests from ubi_block.rs
977	#[test]
978	fn test_ubi_block_correctness() {
979		// Test various chaining value, tweak, and block combinations
980		let test_cases = [
981			// (chaining_value, tweak, block, description)
982			(
983				[
984					0x0123456789ABCDEFu64,
985					0xFEDCBA9876543210u64,
986					0x1111111111111111u64,
987					0x2222222222222222u64,
988					0x3333333333333333u64,
989					0x4444444444444444u64,
990					0x5555555555555555u64,
991					0x6666666666666666u64,
992				],
993				[0xAAAAAAAAAAAAAAAAu64, 0xBBBBBBBBBBBBBBBBu64],
994				[
995					0x0F0E0D0C0B0A0908u64,
996					0x0706050403020100u64,
997					0x1111111111111111u64,
998					0x2222222222222222u64,
999					0x3333333333333333u64,
1000					0x4444444444444444u64,
1001					0x5555555555555555u64,
1002					0x6666666666666666u64,
1003				],
1004				"Basic pattern",
1005			),
1006			([0u64; 8], [0u64; 2], [u64::MAX; 8], "All zeros chaining/tweak, all ones block"),
1007			([u64::MAX; 8], [u64::MAX; 2], [0u64; 8], "All ones chaining/tweak, all zeros block"),
1008		];
1009
1010		for (chaining_value, tweak, block, description) in test_cases {
1011			let expected = reference::ubi_block(chaining_value, tweak, block);
1012
1013			let circuit = CircuitBuilder::new();
1014			let chaining_wires = std::array::from_fn(|_| circuit.add_witness());
1015			let tweak_wires = std::array::from_fn(|_| circuit.add_witness());
1016			let block_wires = std::array::from_fn(|_| circuit.add_witness());
1017
1018			let g_out = ubi_block(&circuit, chaining_wires, tweak_wires, block_wires);
1019
1020			for i in 0..8 {
1021				let expected_wire = circuit.add_constant(Word(expected[i]));
1022				circuit.assert_eq(format!("{}[{}]", description, i), g_out[i], expected_wire);
1023			}
1024
1025			let built_circuit = circuit.build();
1026			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
1027
1028			let mut witness = built_circuit.new_witness_filler();
1029
1030			for (i, &val) in chaining_value.iter().enumerate() {
1031				witness[chaining_wires[i]] = Word(val);
1032			}
1033			for (i, &val) in tweak.iter().enumerate() {
1034				witness[tweak_wires[i]] = Word(val);
1035			}
1036			for (i, &val) in block.iter().enumerate() {
1037				witness[block_wires[i]] = Word(val);
1038			}
1039
1040			built_circuit.populate_wire_witness(&mut witness).unwrap();
1041
1042			let cs = built_circuit.constraint_system();
1043			cs.verify(&witness.into_value_vec())
1044				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
1045		}
1046	}
1047
1048	// Tests from threefish_4rounds_with_injection.rs
1049	#[test]
1050	fn test_threefish_4rounds_with_injection_correctness() {
1051		let test_cases = [
1052			// (group_idx, input_state, k, t, description)
1053			(
1054				0,
1055				[
1056					0x0123456789ABCDEFu64,
1057					0xFEDCBA9876543210u64,
1058					0x1111111111111111u64,
1059					0x2222222222222222u64,
1060					0x3333333333333333u64,
1061					0x4444444444444444u64,
1062					0x5555555555555555u64,
1063					0x6666666666666666u64,
1064				],
1065				[
1066					0x0F0E0D0C0B0A0908u64,
1067					0x0706050403020100u64,
1068					0x1111111111111111u64,
1069					0x2222222222222222u64,
1070					0x3333333333333333u64,
1071					0x4444444444444444u64,
1072					0x5555555555555555u64,
1073					0x6666666666666666u64,
1074					0x7777777777777777u64,
1075				],
1076				[
1077					0xAAAAAAAAAAAAAAAAu64,
1078					0xBBBBBBBBBBBBBBBBu64,
1079					0x1111111111111111u64,
1080				],
1081				"Group 0, sequential pattern",
1082			),
1083			(
1084				5,
1085				[
1086					0xDEADBEEFCAFEBABEu64,
1087					0x0123456789ABCDEFu64,
1088					0xFEDCBA9876543210u64,
1089					0x1111111111111111u64,
1090					0x2222222222222222u64,
1091					0x3333333333333333u64,
1092					0x4444444444444444u64,
1093					0x5555555555555555u64,
1094				],
1095				[0x1111111111111111u64; 9],
1096				[
1097					0x2222222222222222u64,
1098					0x3333333333333333u64,
1099					0x1111111111111111u64,
1100				],
1101				"Group 5, mixed pattern",
1102			),
1103			(17, [0u64; 8], [u64::MAX; 9], [u64::MAX; 3], "Group 17, all zeros and all ones"),
1104		];
1105
1106		for (group_idx, v_in, k, t, description) in test_cases {
1107			let expected = reference::threefish_4rounds_with_injection(v_in, k, t, group_idx);
1108
1109			let circuit = CircuitBuilder::new();
1110			let v_in_wires = std::array::from_fn(|_| circuit.add_witness());
1111			let k_wires = std::array::from_fn(|_| circuit.add_witness());
1112			let t_wires = std::array::from_fn(|_| circuit.add_witness());
1113
1114			let comp = Threefish4RoundsWithInjection::new(
1115				&circuit, v_in_wires, k_wires, t_wires, group_idx,
1116			);
1117
1118			for i in 0..8 {
1119				let expected_wire = circuit.add_constant(Word(expected[i]));
1120				circuit.assert_eq(format!("{}[{}]", description, i), comp.v_out[i], expected_wire);
1121			}
1122
1123			let built_circuit = circuit.build();
1124			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
1125
1126			let mut witness = built_circuit.new_witness_filler();
1127
1128			for (i, &val) in v_in.iter().enumerate() {
1129				witness[v_in_wires[i]] = Word(val);
1130			}
1131			for (i, &val) in k.iter().enumerate() {
1132				witness[k_wires[i]] = Word(val);
1133			}
1134			for (i, &val) in t.iter().enumerate() {
1135				witness[t_wires[i]] = Word(val);
1136			}
1137
1138			built_circuit.populate_wire_witness(&mut witness).unwrap();
1139
1140			let cs = built_circuit.constraint_system();
1141			cs.verify(&witness.into_value_vec())
1142				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
1143		}
1144	}
1145
1146	// Tests from threefish512_block.rs
1147	#[test]
1148	fn test_threefish512_block_correctness() {
1149		// Test various key/tweak/block combinations
1150		let test_cases = [
1151			// (key, tweak, block, description)
1152			(
1153				[
1154					0x0123456789ABCDEFu64,
1155					0xFEDCBA9876543210u64,
1156					0x1111111111111111u64,
1157					0x2222222222222222u64,
1158					0x3333333333333333u64,
1159					0x4444444444444444u64,
1160					0x5555555555555555u64,
1161					0x6666666666666666u64,
1162				],
1163				[0xAAAAAAAAAAAAAAAAu64, 0xBBBBBBBBBBBBBBBBu64],
1164				[
1165					0x0F0E0D0C0B0A0908u64,
1166					0x0706050403020100u64,
1167					0x1111111111111111u64,
1168					0x2222222222222222u64,
1169					0x3333333333333333u64,
1170					0x4444444444444444u64,
1171					0x5555555555555555u64,
1172					0x6666666666666666u64,
1173				],
1174				"Basic pattern",
1175			),
1176			([0u64; 8], [0u64; 2], [u64::MAX; 8], "All zeros key/tweak, all ones block"),
1177			([u64::MAX; 8], [u64::MAX; 2], [0u64; 8], "All ones key/tweak, all zeros block"),
1178		];
1179
1180		for (key, tweak, block, description) in test_cases {
1181			let expected = reference::threefish512_block(key, tweak, block);
1182
1183			let circuit = CircuitBuilder::new();
1184			let key_wires = std::array::from_fn(|_| circuit.add_witness());
1185			let tweak_wires = std::array::from_fn(|_| circuit.add_witness());
1186			let block_wires = std::array::from_fn(|_| circuit.add_witness());
1187
1188			let comp = Threefish512Block::new(&circuit, key_wires, tweak_wires, block_wires);
1189
1190			for i in 0..8 {
1191				let expected_wire = circuit.add_constant(Word(expected[i]));
1192				circuit.assert_eq(format!("{}[{}]", description, i), comp.v_out[i], expected_wire);
1193			}
1194
1195			let built_circuit = circuit.build();
1196			assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
1197
1198			let mut witness = built_circuit.new_witness_filler();
1199
1200			for (i, &val) in key.iter().enumerate() {
1201				witness[key_wires[i]] = Word(val);
1202			}
1203			for (i, &val) in tweak.iter().enumerate() {
1204				witness[tweak_wires[i]] = Word(val);
1205			}
1206			for (i, &val) in block.iter().enumerate() {
1207				witness[block_wires[i]] = Word(val);
1208			}
1209
1210			built_circuit.populate_wire_witness(&mut witness).unwrap();
1211
1212			let cs = built_circuit.constraint_system();
1213			cs.verify(&witness.into_value_vec())
1214				.unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
1215		}
1216	}
1217}