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}