Skip to main content

binius_frontend/
util.rs

1// Copyright 2025 Irreducible Inc.
2
3//! Various utilities for circuit building.
4
5use std::iter;
6
7use binius_core::{Word, consts::WORD_SIZE_BITS};
8
9use crate::compiler::{CircuitBuilder, Wire, circuit::WitnessFiller};
10
11/// Populate the given wires from bytes using little-endian packed 64-bit words.
12///
13/// If `bytes` is not a multiple of 8, the last word is zero-padded.
14///
15/// If there are more wires than needed to hold all bytes, the remaining wires
16/// are filled with `Word::ZERO`.
17///
18/// # Panics
19/// * If bytes.len() exceeds wires.len() * 8
20pub fn pack_bytes_into_wires_le(w: &mut WitnessFiller, wires: &[Wire], bytes: &[u8]) {
21	let max_value_size = wires.len() * 8;
22	assert!(
23		bytes.len() <= max_value_size,
24		"bytes length {} exceeds maximum {}",
25		bytes.len(),
26		max_value_size
27	);
28
29	// Pack bytes into words
30	for (&wire, chunk) in iter::zip(wires, bytes.chunks(8)) {
31		let mut chunk_arr = [0u8; 8];
32		chunk_arr[..chunk.len()].copy_from_slice(chunk);
33		w[wire] = Word(u64::from_le_bytes(chunk_arr));
34	}
35
36	// Zero out remaining words
37	for &wire in &wires[bytes.len().div_ceil(8)..] {
38		w[wire] = Word::ZERO;
39	}
40}
41
42/// Pack bytes into constant wires using little-endian packed 64-bit words.
43///
44/// Creates and returns a vector of constant wires containing the packed byte data.
45/// If `bytes` is not a multiple of 8, the last word is zero-padded.
46///
47/// # Example
48/// ```ignore
49/// let bytes = b"Hello";
50/// let wires = pack_bytes_into_const_wires_le(&builder, bytes);
51/// // wires.len() == 1 (5 bytes packed into 1 word, zero-padded)
52/// ```
53pub fn pack_bytes_into_const_wires_le(b: &CircuitBuilder, bytes: &[u8]) -> Vec<Wire> {
54	bytes
55		.chunks(8)
56		.map(|chunk| {
57			let mut chunk_arr = [0u8; 8];
58			chunk_arr[..chunk.len()].copy_from_slice(chunk);
59			let word = Word(u64::from_le_bytes(chunk_arr));
60			b.add_constant(word)
61		})
62		.collect()
63}
64
65/// Returns a BigUint from u64 limbs with little-endian ordering
66pub fn num_biguint_from_u64_limbs<I>(limbs: I) -> num_bigint::BigUint
67where
68	I: IntoIterator,
69	I::Item: std::borrow::Borrow<u64>,
70	I::IntoIter: ExactSizeIterator,
71{
72	use std::borrow::Borrow;
73
74	use num_bigint::BigUint;
75
76	let iter = limbs.into_iter();
77	// Each u64 becomes two u32s (low word first for little-endian)
78	let mut digits = Vec::with_capacity(iter.len() * 2);
79	for item in iter {
80		let double_digit = *item.borrow();
81		// push:
82		// - low 32 bits
83		// - high 32 bits
84		digits.push(double_digit as u32);
85		digits.push((double_digit >> 32) as u32);
86	}
87	BigUint::new(digits)
88}
89
90/// Check that all boolean wires in an iterable are simultaneously true.
91pub fn all_true(b: &CircuitBuilder, booleans: impl IntoIterator<Item = Wire>) -> Wire {
92	booleans
93		.into_iter()
94		.fold(b.add_constant(Word::ALL_ONE), |lhs, rhs| b.band(lhs, rhs))
95}
96
97/// Convert MSB-bool into an all-1/all-0 mask.
98pub fn bool_to_mask(b: &CircuitBuilder, boolean: Wire) -> Wire {
99	b.sar(boolean, (WORD_SIZE_BITS - 1) as u32)
100}
101
102/// Swap the byte order of the word.
103///
104/// Breaks the word down to bytes and reassembles in reversed order.
105pub fn byteswap(b: &CircuitBuilder, word: Wire) -> Wire {
106	let bytes = (0..8).map(|j| {
107		let byte = b.extract_byte(word, j as u32);
108		b.shl(byte, (56 - 8 * j) as u32)
109	});
110	bytes
111		.reduce(|lhs, rhs| b.bxor(lhs, rhs))
112		.expect("WORD_SIZE_BITS > 0")
113}
114
115#[cfg(test)]
116mod tests {
117	use super::*;
118
119	/// Helper to test pack_bytes_into_const_wires_le with expected word values
120	fn test_pack_bytes(bytes: &[u8], expected_words: &[u64]) {
121		let b = CircuitBuilder::new();
122		let wires = pack_bytes_into_const_wires_le(&b, bytes);
123
124		assert_eq!(wires.len(), expected_words.len());
125
126		let circuit = b.build();
127		let mut filler = circuit.new_witness_filler();
128		circuit.populate_wire_witness(&mut filler).unwrap();
129
130		for (wire, &expected) in wires.iter().zip(expected_words) {
131			assert_eq!(filler[*wire], Word(expected));
132		}
133	}
134
135	#[test]
136	fn test_pack_bytes_into_const_wires_le_aligned() {
137		// Test with exactly 8 bytes (1 word)
138		let bytes = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
139		test_pack_bytes(&bytes, &[0x0807060504030201]);
140	}
141
142	#[test]
143	fn test_pack_bytes_into_const_wires_le_partial() {
144		// Test with 5 bytes (partial word)
145		// "Hello" in little-endian with zero padding
146		// H=0x48, e=0x65, l=0x6c, l=0x6c, o=0x6f
147		test_pack_bytes(b"Hello", &[0x6f6c6c6548]);
148	}
149
150	#[test]
151	fn test_pack_bytes_into_const_wires_le_multiple_words() {
152		// Test with 16 bytes (2 words)
153		let bytes: Vec<u8> = (0..16).collect();
154		test_pack_bytes(&bytes, &[0x0706050403020100, 0x0f0e0d0c0b0a0908]);
155	}
156
157	#[test]
158	fn test_pack_bytes_into_const_wires_le_empty() {
159		// Test with empty bytes
160		test_pack_bytes(&[], &[]);
161	}
162
163	#[test]
164	fn test_pack_bytes_into_const_wires_le_unaligned_multi() {
165		// Test with 11 bytes (1 full word + partial second word, zero-padded)
166		let bytes: Vec<u8> = (0..11).collect();
167		test_pack_bytes(&bytes, &[0x0706050403020100, 0x0a0908]);
168	}
169}