Skip to main content

binius_circuits/bignum/
addsub.rs

1// Copyright 2025 Irreducible Inc.
2use binius_core::word::Word;
3use binius_frontend::{CircuitBuilder, Wire};
4
5use super::biguint::BigUint;
6
7/// Add two equally-sized `BigUints`s with carry propagation.
8///
9/// See `add_with_carry_out`; this routine additionally asserts that no overflow happens.
10pub fn add(builder: &CircuitBuilder, a: &BigUint, b: &BigUint) -> BigUint {
11	let (sum, carry_out) = add_with_carry_out(builder, a, b);
12	// Assert that no overflow happened.
13	builder.assert_false("add_carry_out_zero", carry_out);
14	sum
15}
16
17/// Add two equally-sized `BigUints`s with carry propagation.
18///
19/// Computes `a + b` with proper carry handling between limbs. The result
20/// has the same number of limbs as the inputs. Overflow beyond the most
21/// significant limb is checked and must be zero.
22///
23/// # Arguments
24/// * `builder` - Circuit builder for constraint generation
25/// * `a` - First operand
26/// * `b` - Second operand (must have same number of limbs as `a`)
27///
28/// # Returns
29/// A tuple of `BigUint` sum of size equal to the inputs and a carry out boolean wire.
30///
31/// # Panics
32/// - Panics if `a` and `b` have different number of limbs
33pub fn add_with_carry_out(builder: &CircuitBuilder, a: &BigUint, b: &BigUint) -> (BigUint, Wire) {
34	assert_eq!(
35		a.limbs.len(),
36		b.limbs.len(),
37		"add_with_carry_out: inputs must have the same number of limbs"
38	);
39
40	let mut accumulator = vec![vec![]; a.limbs.len()];
41	for i in 0..a.limbs.len() {
42		accumulator[i].push(a.limbs[i]);
43		accumulator[i].push(b.limbs[i]);
44	}
45
46	let (sum, carry_out) = compute_stack_adds_with_carry_outs(builder, &accumulator);
47	assert_eq!(carry_out.len(), if a.limbs.is_empty() { 0 } else { 1 });
48	(
49		sum,
50		carry_out
51			.first()
52			.copied()
53			.unwrap_or_else(|| builder.add_constant(Word::ZERO)),
54	)
55}
56
57/// Subtracts two equally-sized `BigUints`s with carry propagation.
58///
59/// Computes `a - b` with proper borrow handling between limbs. The result
60/// has the same number of limbs as the inputs. Underflow beyond the most
61/// significant limb is checked and must be zero. This implies that `a >= b`
62/// and the difference remains unsigned.
63///
64/// # Arguments
65/// * `builder` - Circuit builder for constraint generation
66/// * `a` - minuend
67/// * `b` - subtrahend (must have same number of limbs as `a`)
68///
69/// # Returns
70/// Difference as a `BigUint` with the same number of limbs as the inputs
71///
72/// # Panics
73/// - Panics if `a` and `b` have different number of limbs
74pub fn sub(builder: &CircuitBuilder, a: &BigUint, b: &BigUint) -> BigUint {
75	let zero = builder.add_constant(Word::ZERO);
76	let (diff, borrow_out) = sub_with_borrow_out(builder, a, b);
77
78	// Assert the final borrow is zero (i.e no underflow).
79	//
80	// It requires checking the MSB of the `borrow_out`
81	let borrow_out_msb = builder.shr(borrow_out, 63);
82	builder.assert_eq("sub_borrow_out", borrow_out_msb, zero);
83
84	diff
85}
86
87/// Subtracts two equally-sized `BigUint`s modulo `2^(64 * limbs)`, returning the final borrow.
88///
89/// Unlike [`sub`], underflow is not rejected: the difference wraps and the returned wire
90/// carries the borrow out of the most significant limb in its MSB.
91pub(super) fn sub_with_borrow_out(
92	builder: &CircuitBuilder,
93	a: &BigUint,
94	b: &BigUint,
95) -> (BigUint, Wire) {
96	assert_eq!(a.limbs.len(), b.limbs.len(), "sub: inputs must have the same number of limbs");
97
98	let zero = builder.add_constant(Word::ZERO);
99
100	let mut diff_limbs = Vec::with_capacity(a.limbs.len());
101
102	let mut borrow_in = zero;
103	for (&a_limb, &b_limb) in a.limbs.iter().zip(&b.limbs) {
104		let (diff_limb, borrow_out) = builder.isub_bin_bout(a_limb, b_limb, borrow_in);
105		diff_limbs.push(diff_limb);
106
107		borrow_in = borrow_out;
108	}
109
110	(BigUint { limbs: diff_limbs }, borrow_in)
111}
112
113/// Computes multi-operand addition with carry propagation across limb positions.
114///
115/// See `compute_stack_adds_with_carry_outs`; this routine additionally asserts that no
116/// carry out happens.
117pub(super) fn compute_stack_adds(builder: &CircuitBuilder, limb_stacks: &[Vec<Wire>]) -> BigUint {
118	let (sum, carry_outs) = compute_stack_adds_with_carry_outs(builder, limb_stacks);
119
120	// Assert all final carries are zero (i.e no overflow).
121	//
122	// It is sufficient to check the MSB of each wire in `carries` because:
123	//
124	// - The `carries` vector stores carry_out from each iadd_cin_cout gate.
125	// - The carry bit for each addition is stored in the MSB of the carry_out wire.
126	for (i, carry_out) in carry_outs.into_iter().enumerate() {
127		builder.assert_false(format!("compute_stack_adds_carry_zero_{i}"), carry_out);
128	}
129
130	sum
131}
132
133/// Computes multi-operand addition with carry propagation across limb positions.
134///
135/// This function is the core of bignum arithmetic, handling the addition of multiple
136/// values at each limb position with proper carry propagation to higher limbs.
137/// It's used by other bignum operations to resolve partial products and sums.
138///
139/// # Arguments
140/// * `builder` - Circuit builder for constraint generation
141/// * `limb_stacks` - Array where `limb_stacks[i]` contains all values to be added at limb position
142///   `i`.
143///
144/// # Returns
145/// The sum produced by the addition chain, as well as a number of boolean wires representing carry
146/// outs in the most significant limb; converting these booleans to 0/1 integers and summing
147/// produces the value of overflow limb.
148pub(super) fn compute_stack_adds_with_carry_outs(
149	builder: &CircuitBuilder,
150	limb_stacks: &[Vec<Wire>],
151) -> (BigUint, Vec<Wire>) {
152	let mut sums = Vec::new();
153	let mut carries = Vec::new();
154	let zero = builder.add_constant(Word::ZERO);
155
156	for limb_stack in limb_stacks {
157		let mut limb_stack = limb_stack.clone();
158		let mut new_carries = Vec::new();
159
160		// Pad stack to handle incoming carries
161		if limb_stack.len() < carries.len() + 1 {
162			limb_stack.resize(carries.len() + 1, zero);
163		}
164
165		while limb_stack.len() >= 2 {
166			let carry_in = carries.pop().unwrap_or(zero);
167			let x = limb_stack.pop().expect("limb_stack.len() >= 2");
168			let y = limb_stack.pop().expect("limb_stack.len() >= 2");
169
170			let (sum, cout) = builder.iadd_cin_cout(x, y, carry_in);
171			limb_stack.push(sum);
172			new_carries.push(cout);
173		}
174
175		sums.push(limb_stack[0]);
176		assert!(limb_stack.len() == 1);
177		assert!(carries.is_empty());
178		carries = new_carries;
179	}
180
181	(BigUint { limbs: sums }, carries)
182}