Skip to main content

binius_circuits/bignum/
biguint.rs

1// Copyright 2025 Irreducible Inc.
2use std::iter;
3
4use binius_core::word::Word;
5use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
6
7/// Represents an arbitrarily large unsigned integer using a vector of `Wire`s
8///
9/// - Each `Wire` holds a 64-bit unsigned integer value (a "limb")
10/// - Limbs are stored in little-endian order (index 0 = least significant)
11/// - The total bit width is always a multiple of 64 bits (number of limbs × 64)
12#[derive(Clone)]
13pub struct BigUint {
14	pub limbs: Vec<Wire>,
15}
16
17impl BigUint {
18	/// Creates a new BigUint with the given number of limbs as inout wires.
19	pub fn new_inout(b: &CircuitBuilder, num_limbs: usize) -> Self {
20		let limbs = (0..num_limbs).map(|_| b.add_inout()).collect();
21		BigUint { limbs }
22	}
23
24	/// Creates a new BigUint with the given number of limbs as witness wires.
25	pub fn new_witness(b: &CircuitBuilder, num_limbs: usize) -> Self {
26		let limbs = (0..num_limbs).map(|_| b.add_witness()).collect();
27		BigUint { limbs }
28	}
29
30	/// Creates a constant BigUint from num_bigint::BigUint.
31	pub fn new_constant(b: &CircuitBuilder, num_biguint: &num_bigint::BigUint) -> Self {
32		let limbs = num_biguint
33			.iter_u64_digits()
34			.map(|limb| b.add_constant_64(limb))
35			.collect();
36		BigUint { limbs }
37	}
38
39	/// Returns zero unless the MSB-boolean `cond` is true, then passes the value unchanged.
40	pub fn zero_unless(&self, b: &CircuitBuilder, cond: Wire) -> Self {
41		let zero = b.add_constant(Word::ZERO);
42		let limbs = self
43			.limbs
44			.iter()
45			.map(|&limb| b.select(cond, limb, zero))
46			.collect();
47		Self { limbs }
48	}
49
50	/// Checks whether BigUint is zero and returns the check result as a boolean wire.
51	pub fn is_zero(&self, b: &CircuitBuilder) -> Wire {
52		let zero = b.add_constant(Word::ZERO);
53		let any_bit = self
54			.limbs
55			.iter()
56			.copied()
57			.reduce(|lhs, rhs| b.bor(lhs, rhs))
58			.unwrap_or(zero);
59		b.icmp_eq(any_bit, zero)
60	}
61
62	/// Pads to given limb length with zeros.
63	///
64	/// No-op if `new_limbs_len` is shorter then the current one.
65	pub fn zero_extend(&self, b: &CircuitBuilder, new_limbs_len: usize) -> Self {
66		let zero = b.add_constant(Word::ZERO);
67		self.pad_limbs_to(new_limbs_len, zero)
68	}
69
70	/// Pads to given limb length with a wire value.
71	///
72	/// No-op if `new_limbs_len` is shorter then the current one.
73	pub fn pad_limbs_to(&self, new_limbs_len: usize, padding_value: Wire) -> Self {
74		let mut padded_limbs = self.limbs.clone();
75		if new_limbs_len > padded_limbs.len() {
76			padded_limbs.resize(new_limbs_len, padding_value);
77		}
78		Self {
79			limbs: padded_limbs,
80		}
81	}
82
83	/// Splits the `BigUint` at a given limb position into `(lo, hi)`. The result
84	/// satisfies `lo + 2^(Word::BITS * lo.limbs.len()) * hi`.
85	pub fn split_at_limbs(mut self, at_limbs: usize) -> (Self, Self) {
86		let hi_limbs = self.limbs.split_off(at_limbs);
87		(self, Self { limbs: hi_limbs })
88	}
89
90	/// Concatenate the limbs of another `BigUint` on top. The resulting value
91	/// equals `self + 2^(Word::BITS * self.limbs.len()) * hi`.
92	pub fn concat_limbs(&self, hi: &Self) -> Self {
93		let mut limbs = self.limbs.clone();
94		limbs.extend(&hi.limbs);
95		Self { limbs }
96	}
97
98	/// Populate the BigUint with the expected limb_values
99	///
100	/// Panics if limb_values.len() != self.limbs.len()
101	pub fn populate_limbs(&self, w: &mut WitnessFiller<'_>, limb_values: &[u64]) {
102		assert!(limb_values.len() == self.limbs.len());
103		for (&wire, &v) in iter::zip(&self.limbs, limb_values) {
104			w[wire] = Word::from_u64(v);
105		}
106	}
107}
108
109/// Asserts that that two `BigUint`s are equal.
110///
111/// # Arguments
112/// * `builder` - Circuit builder for constraint generation
113/// * `a` - First operand `BigUint`
114/// * `b` - Second operand `BigUint` (must have same number of limbs as `a`)
115///
116/// # Panics
117/// Panics if `a` and `b` have different number of limbs.
118pub fn assert_eq(builder: &CircuitBuilder, name: impl Into<String>, a: &BigUint, b: &BigUint) {
119	assert_eq!(
120		a.limbs.len(),
121		b.limbs.len(),
122		"biguint assert_eq: inputs must have the same number of limbs"
123	);
124	let base_name = name.into();
125	for (i, (&a_l, &b_l)) in iter::zip(&a.limbs, &b.limbs).enumerate() {
126		builder.assert_eq(format!("{base_name}[{i}]"), a_l, b_l);
127	}
128}
129
130/// Conditionally asserts that that two `BigUint`s are equal.
131///
132/// # Arguments
133/// * `builder` - Circuit builder for constraint generation
134/// * `a` - First operand `BigUint`
135/// * `b` - Second operand `BigUint` (must have same number of limbs as `a`)
136/// * `cond` - a must equal b if cond is msb-true; constraint is ignored if cond is msb-false
137///
138/// # Panics
139/// Panics if `a` and `b` have different number of limbs.
140pub fn assert_eq_cond(
141	builder: &CircuitBuilder,
142	name: impl Into<String>,
143	a: &BigUint,
144	b: &BigUint,
145	cond: Wire,
146) {
147	assert_eq!(
148		a.limbs.len(),
149		b.limbs.len(),
150		"biguint assert_eq_cond: inputs must have the same number of limbs"
151	);
152	let base_name = name.into();
153	for (i, (&a_l, &b_l)) in iter::zip(&a.limbs, &b.limbs).enumerate() {
154		builder.assert_eq_cond(format!("{base_name}[{i}]"), a_l, b_l, cond);
155	}
156}
157
158/// Conditionally selects between two equal-sized `BigUint`s.
159///
160/// # Arguments
161/// * `builder` - Circuit builder for constraint generation
162/// * `cond` - an MSB-boolean
163/// * `t` - Value to select when cond is true (MSB=1)
164/// * `f` - Value to select when cond is false (MSB=0)
165///
166/// # Return value
167/// Selects `t` if `cond` is true, otherwise selects `f`.
168///
169/// # Panics
170/// Panics if `t` and `f` have different number of limbs.
171pub fn select(builder: &CircuitBuilder, cond: Wire, t: &BigUint, f: &BigUint) -> BigUint {
172	assert_eq!(
173		t.limbs.len(),
174		f.limbs.len(),
175		"biguint select: inputs must have the same number of limbs"
176	);
177
178	let limbs = iter::zip(&t.limbs, &f.limbs)
179		.map(|(&l1, &l2)| builder.select(cond, l1, l2))
180		.collect();
181	BigUint { limbs }
182}
183
184/// Builds a [`num_bigint::BigUint`] from little-endian `u64` limbs.
185///
186/// The limbs are little-endian and each limb is itself little-endian, so their
187/// concatenated little-endian bytes are the value's little-endian byte string.
188pub fn num_biguint_from_u64_limbs<I>(limbs: I) -> num_bigint::BigUint
189where
190	I: IntoIterator,
191	I::Item: std::borrow::Borrow<u64>,
192{
193	use std::borrow::Borrow;
194
195	let bytes: Vec<u8> = limbs
196		.into_iter()
197		.flat_map(|limb| limb.borrow().to_le_bytes())
198		.collect();
199	num_bigint::BigUint::from_bytes_le(&bytes)
200}