Skip to main content

binius_circuits/
bytes.rs

1// Copyright 2025 Irreducible Inc.
2//! Byte manipulation circuits for Binius64.
3//!
4//! This module provides utility functions for byte-level operations on 64-bit words,
5//! including byte swapping (endianness conversion).
6
7use binius_frontend::{CircuitBuilder, Wire};
8
9/// Swaps bytes within each 32-bit half of a 64-bit word independently.
10///
11/// This function performs byte swapping on the high and low 32-bit halves
12/// of a 64-bit word in parallel. Each half has its byte order reversed
13/// independently, without affecting the other half.
14///
15/// # Algorithm
16///
17/// The implementation uses the Hacker's Delight algorithm applied to both
18/// 32-bit halves simultaneously:
19///
20/// 1. **Pass 1**: Swap adjacent bytes within each 32-bit half
21///    - Mask: `0x00FF00FF00FF00FF`
22///    - Operation: `((x & mask) << 8) | ((x >> 8) & mask)`
23///
24/// 2. **Pass 2**: Swap adjacent 16-bit units within each 32-bit half
25///    - Mask: `0x0000FFFF0000FFFF`
26///    - Operation: `((x & mask) << 16) | ((x >> 16) & mask)`
27///
28/// # Arguments
29/// * `builder` - The circuit builder to add constraints to
30/// * `input` - Wire containing the 64-bit value to process
31///
32/// # Returns
33/// * Wire containing the result with bytes swapped within each 32-bit half
34///
35/// # Example
36/// Input:  `0x0123456789ABCDEF` (bytes: 01 23 45 67 | 89 AB CD EF)
37/// Output: `0x6745230189ABCDEF` (bytes: 67 45 23 01 | EF CD AB 89)
38pub fn swap_bytes_32(builder: &CircuitBuilder, input: Wire) -> Wire {
39	// Create constant masks for each pass
40	let mask_00ff = builder.add_constant_64(0x00FF00FF00FF00FF);
41	let mask_0000ffff = builder.add_constant_64(0x0000FFFF0000FFFF);
42
43	// Pass 1: Swap adjacent bytes within each 32-bit half
44	// x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF)
45	let masked_input_bytes = builder.band(input, mask_00ff);
46	let shl_8 = builder.shl(masked_input_bytes, 8);
47	let shr_8 = builder.shr(input, 8);
48	let masked_shr_8 = builder.band(shr_8, mask_00ff);
49	let step1 = builder.bxor(shl_8, masked_shr_8);
50
51	// Pass 2: Swap adjacent 16-bit units within each 32-bit half
52	// x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF)
53	let masked_step1_words = builder.band(step1, mask_0000ffff);
54	let shl_16 = builder.shl(masked_step1_words, 16);
55	let shr_16 = builder.shr(step1, 16);
56	let masked_shr_16 = builder.band(shr_16, mask_0000ffff);
57	builder.bxor(shl_16, masked_shr_16)
58}
59
60/// Reverses the byte order of a 64-bit word.
61///
62/// This function swaps the bytes of the input word, converting between
63/// little-endian and big-endian representations. It implements the same
64/// operation as the Rust standard library's `u64::swap_bytes()`.
65///
66/// # Algorithm
67///
68/// The implementation is decomposed into two steps:
69/// 1. Swap bytes within each 32-bit half independently using `swap_bytes_32`
70/// 2. Rotate the entire word by 32 bits to swap the halves
71///
72/// This is equivalent to the full Hacker's Delight byte reversal algorithm
73/// but expressed more modularly.
74///
75/// # Arguments
76/// * `builder` - The circuit builder to add constraints to
77/// * `input` - Wire containing the 64-bit value to swap bytes of
78///
79/// # Returns
80/// * Wire containing the byte-swapped result
81///
82/// # Cost Analysis
83/// * Uses `swap_bytes_32`: 4 shifts, 4 ANDs, 2 XORs
84/// * Plus 1 rotation (implemented as 2 shifts + 1 XOR internally)
85/// * Total: 6 shift operations, 4 AND operations, 3 XOR operations
86///
87/// All shifts are free in Binius64 when part of constraints, making this
88/// approach very efficient.
89///
90/// # Example
91///
92/// ```rust,ignore
93/// use binius_core::word::Word;
94/// use binius_frontend::crate::bytes::swap_bytes;
95/// use binius_frontend::compiler::CircuitBuilder;
96///
97/// // Build circuit
98/// let mut builder = CircuitBuilder::new();
99/// let input = builder.add_witness();
100/// let output = builder.add_witness();
101/// let swapped = swap_bytes(&builder, input);
102/// builder.assert_eq("swap_bytes_result", swapped, output);
103/// let circuit = builder.build();
104///
105/// // Fill witness
106/// let mut w = circuit.new_witness_filler();
107/// w[input] = Word(0x0123456789ABCDEF);
108/// w[output] = Word(0xEFCDAB8967452301);  // Bytes reversed
109///
110/// // Verify
111/// circuit.populate_wire_witness(&mut w).unwrap();
112/// ```
113///
114/// # Reference
115/// Based on the byte swapping algorithm from "Hacker's Delight" by Henry S. Warren Jr.
116pub fn swap_bytes(builder: &CircuitBuilder, input: Wire) -> Wire {
117	// Step 1: Swap bytes within each 32-bit half independently
118	let swapped_halves = swap_bytes_32(builder, input);
119
120	// Step 2: Rotate by 32 bits to swap the two halves
121	// This completes the full byte reversal
122	builder.rotl(swapped_halves, 32)
123}
124
125#[cfg(test)]
126mod tests {
127	use binius_core::word::Word;
128	use proptest::prelude::*;
129
130	use super::*;
131
132	/// Helper function to test swap_bytes circuit with given input and expected output
133	fn test_swap_bytes_helper(input_val: u64, expected: u64) {
134		let builder = CircuitBuilder::new();
135		let input = builder.add_witness();
136		let output = builder.add_witness();
137		let swapped = swap_bytes(&builder, input);
138		builder.assert_eq("swap_bytes_result", swapped, output);
139		let circuit = builder.build();
140
141		let mut w = circuit.new_witness_filler();
142		w[input] = Word(input_val);
143		w[output] = Word(expected);
144
145		circuit.populate_wire_witness(&mut w).unwrap();
146		circuit
147			.constraint_system()
148			.verify(&w.into_value_vec())
149			.unwrap();
150	}
151
152	/// Helper function to test swap_bytes_32 circuit with given input and expected output
153	fn test_swap_bytes_32_helper(input_val: u64, expected: u64) {
154		let builder = CircuitBuilder::new();
155		let input = builder.add_witness();
156		let output = builder.add_witness();
157		let swapped = swap_bytes_32(&builder, input);
158		builder.assert_eq("swap_bytes_32_result", swapped, output);
159		let circuit = builder.build();
160
161		let mut w = circuit.new_witness_filler();
162		w[input] = Word(input_val);
163		w[output] = Word(expected);
164
165		circuit.populate_wire_witness(&mut w).unwrap();
166		circuit
167			.constraint_system()
168			.verify(&w.into_value_vec())
169			.unwrap();
170	}
171
172	proptest! {
173		#[test]
174		fn test_swap_bytes_random(input_val: u64) {
175			let expected = input_val.swap_bytes();
176			test_swap_bytes_helper(input_val, expected);
177		}
178
179		#[test]
180		fn test_swap_bytes_32_random(input_val: u64) {
181			// For each 32-bit half, swap its bytes
182			let lo = (input_val & 0xFFFFFFFF) as u32;
183			let hi = ((input_val >> 32) & 0xFFFFFFFF) as u32;
184			let swapped_lo = lo.swap_bytes() as u64;
185			let swapped_hi = (hi.swap_bytes() as u64) << 32;
186			let expected = swapped_hi | swapped_lo;
187			test_swap_bytes_32_helper(input_val, expected);
188		}
189	}
190}