Skip to content

Commit d6d9a96

Browse files
add functions that compute packed reduced qm31 arithmetics to math_utils
1 parent a10d51d commit d6d9a96

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#### Upcoming Changes
44

5+
* feat: add functions that compute packed reduced qm31 arithmetics to `math_utils` [#1944](https://github.com/lambdaclass/cairo-vm/pull/1944)
6+
57
* feat: set `encoded_instruction` to be u128 for opcode_extensions to come [#1940](https://github.com/lambdaclass/cairo-vm/pull/1940)
68

79
* feat: prepare `rust.yml` and `MakeFile` for the folder `stwo_exclusive_programs` [#1939](https://github.com/lambdaclass/cairo-vm/pull/1939)

vm/src/math_utils/mod.rs

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ lazy_static! {
2424
.collect::<Vec<_>>();
2525
}
2626

27+
const STWO_PRIME: u128 = (1 << 31) - 1;
28+
2729
/// Returns the `n`th (up to the `251`th power) power of 2 as a [`Felt252`]
2830
/// in constant time.
2931
/// It silently returns `1` if the input is out of bounds.
@@ -66,6 +68,181 @@ pub fn signed_felt(felt: Felt252) -> BigInt {
6668
}
6769
}
6870

71+
/// Reads four u128 coordinates from a single Felt252.
72+
/// Returns an error if the input has over 144 bits or any coordinate is unreduced.
73+
fn qm31_packed_reduced_read_coordinates(felt: Felt252) -> Result<[u128; 4], MathError> {
74+
let limbs = felt.to_le_digits();
75+
if limbs[3] != 0 || limbs[2] >= 1 << 16 {
76+
return Err(MathError::QM31UpackingError(Box::new(felt)));
77+
}
78+
let coordinates = [
79+
(limbs[0] & ((1 << 36) - 1)) as u128,
80+
((limbs[0] >> 36) + ((limbs[1] & ((1 << 8) - 1)) << 28)) as u128,
81+
((limbs[1] >> 8) & ((1 << 36) - 1)) as u128,
82+
((limbs[1] >> 44) + (limbs[2] << 20)) as u128,
83+
];
84+
for x in coordinates.iter() {
85+
if *x >= STWO_PRIME {
86+
return Err(MathError::QM31UnreducedError(Box::new(felt)));
87+
}
88+
}
89+
Ok(coordinates)
90+
}
91+
92+
/// Reduces four u128 coordinates and packs them into a single Felt252.
93+
fn qm31_coordinates_to_packed_reduced(coordinates: [u128; 4]) -> Felt252 {
94+
let bytes_part1 =
95+
((coordinates[0] % STWO_PRIME) + ((coordinates[1] % STWO_PRIME) << 36)).to_le_bytes();
96+
let bytes_part2 =
97+
((coordinates[2] % STWO_PRIME) + ((coordinates[3] % STWO_PRIME) << 36)).to_le_bytes();
98+
let mut result_bytes = [0u8; 32];
99+
result_bytes[0..9].copy_from_slice(&bytes_part1[0..9]);
100+
result_bytes[9..18].copy_from_slice(&bytes_part2[0..9]);
101+
Felt252::from_bytes_le(&result_bytes)
102+
}
103+
104+
/// Computes the addition of two QM31 elements in reduced form.
105+
/// Returns an error if either operand is not reduced.
106+
pub fn qm31_packed_reduced_add(felt1: Felt252, felt2: Felt252) -> Result<Felt252, MathError> {
107+
let coordinates1 = qm31_packed_reduced_read_coordinates(felt1)?;
108+
let coordinates2 = qm31_packed_reduced_read_coordinates(felt2)?;
109+
let result_unreduced_coordinates = [
110+
coordinates1[0] + coordinates2[0],
111+
coordinates1[1] + coordinates2[1],
112+
coordinates1[2] + coordinates2[2],
113+
coordinates1[3] + coordinates2[3],
114+
];
115+
Ok(qm31_coordinates_to_packed_reduced(
116+
result_unreduced_coordinates,
117+
))
118+
}
119+
120+
/// Computes the negative of a QM31 element in reduced form.
121+
/// Returns an error if the input is not reduced.
122+
pub fn qm31_packed_reduced_neg(felt: Felt252) -> Result<Felt252, MathError> {
123+
let coordinates = qm31_packed_reduced_read_coordinates(felt)?;
124+
Ok(qm31_coordinates_to_packed_reduced([
125+
STWO_PRIME - coordinates[0],
126+
STWO_PRIME - coordinates[1],
127+
STWO_PRIME - coordinates[2],
128+
STWO_PRIME - coordinates[3],
129+
]))
130+
}
131+
132+
/// Computes the subtraction of two QM31 elements in reduced form.
133+
/// Returns an error if either operand is not reduced.
134+
pub fn qm31_packed_reduced_sub(felt1: Felt252, felt2: Felt252) -> Result<Felt252, MathError> {
135+
let coordinates1 = qm31_packed_reduced_read_coordinates(felt1)?;
136+
let coordinates2 = qm31_packed_reduced_read_coordinates(felt2)?;
137+
let result_unreduced_coordinates = [
138+
STWO_PRIME + coordinates1[0] - coordinates2[0],
139+
STWO_PRIME + coordinates1[1] - coordinates2[1],
140+
STWO_PRIME + coordinates1[2] - coordinates2[2],
141+
STWO_PRIME + coordinates1[3] - coordinates2[3],
142+
];
143+
Ok(qm31_coordinates_to_packed_reduced(
144+
result_unreduced_coordinates,
145+
))
146+
}
147+
148+
/// Computes the multiplication of two QM31 elements in reduced form.
149+
/// Returns an error if either operand is not reduced.
150+
pub fn qm31_packed_reduced_mul(felt1: Felt252, felt2: Felt252) -> Result<Felt252, MathError> {
151+
let coordinates1 = qm31_packed_reduced_read_coordinates(felt1)?;
152+
let coordinates2 = qm31_packed_reduced_read_coordinates(felt2)?;
153+
let result_unreduced_coordinates = [
154+
coordinates1[0] * coordinates2[0] + STWO_PRIME * STWO_PRIME
155+
- coordinates1[1] * coordinates2[1]
156+
+ 2 * (coordinates1[2] * coordinates2[2] + STWO_PRIME * STWO_PRIME
157+
- coordinates1[3] * coordinates2[3])
158+
- coordinates1[2] * coordinates2[3]
159+
- coordinates1[3] * coordinates2[2],
160+
coordinates1[0] * coordinates2[1]
161+
+ coordinates2[0] * coordinates1[1]
162+
+ 2 * (coordinates1[2] * coordinates2[3] + coordinates1[3] * coordinates2[2])
163+
+ coordinates1[2] * coordinates2[2]
164+
- coordinates1[3] * coordinates2[3],
165+
coordinates1[0] * coordinates2[2] + STWO_PRIME * STWO_PRIME
166+
- coordinates1[1] * coordinates2[3]
167+
+ coordinates1[2] * coordinates2[0]
168+
- coordinates1[3] * coordinates2[1],
169+
coordinates1[0] * coordinates2[3]
170+
+ coordinates1[1] * coordinates2[2]
171+
+ coordinates1[2] * coordinates2[1]
172+
+ coordinates1[3] * coordinates2[0],
173+
];
174+
Ok(qm31_coordinates_to_packed_reduced(
175+
result_unreduced_coordinates,
176+
))
177+
}
178+
179+
/// Computes `v^(STWO_PRIME-2) modulo STWO_PRIME`.
180+
pub fn pow2147483645(v: u128) -> u128 {
181+
let t0 = (sqn(v, 2) * v) % STWO_PRIME;
182+
let t1 = (sqn(t0, 1) * t0) % STWO_PRIME;
183+
let t2 = (sqn(t1, 3) * t0) % STWO_PRIME;
184+
let t3 = (sqn(t2, 1) * t0) % STWO_PRIME;
185+
let t4 = (sqn(t3, 8) * t3) % STWO_PRIME;
186+
let t5 = (sqn(t4, 8) * t3) % STWO_PRIME;
187+
(sqn(t5, 7) * t2) % STWO_PRIME
188+
}
189+
190+
/// Computes `v^(2*n) modulo STWO_PRIME`.
191+
fn sqn(v: u128, n: usize) -> u128 {
192+
let mut u = v;
193+
for _ in 0..n {
194+
u = (u * u) % STWO_PRIME;
195+
}
196+
u
197+
}
198+
199+
/// Computes the inverse of a QM31 element in reduced form.
200+
/// Returns an error if the denominator is zero or either operand is not reduced.
201+
pub fn qm31_packed_reduced_inv(felt: Felt252) -> Result<Felt252, MathError> {
202+
if felt.is_zero() {
203+
return Err(MathError::DividedByZero);
204+
}
205+
let coordinates = qm31_packed_reduced_read_coordinates(felt)?;
206+
207+
let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME
208+
- coordinates[3] * coordinates[3])
209+
% STWO_PRIME;
210+
let b2_i = (2 * coordinates[2] * coordinates[3]) % STWO_PRIME;
211+
212+
let denom_r = (coordinates[0] * coordinates[0] + STWO_PRIME * STWO_PRIME
213+
- coordinates[1] * coordinates[1]
214+
+ 2 * STWO_PRIME
215+
- 2 * b2_r
216+
+ b2_i)
217+
% STWO_PRIME;
218+
let denom_i =
219+
(2 * coordinates[0] * coordinates[1] + 3 * STWO_PRIME - 2 * b2_i - b2_r) % STWO_PRIME;
220+
221+
let denom_norm_squared = (denom_r * denom_r + denom_i * denom_i) % STWO_PRIME;
222+
let denom_norm_inverse_squared = pow2147483645(denom_norm_squared);
223+
224+
let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME;
225+
let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME;
226+
227+
Ok(qm31_coordinates_to_packed_reduced([
228+
coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME
229+
- coordinates[1] * denom_inverse_i,
230+
coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r,
231+
coordinates[3] * denom_inverse_i + STWO_PRIME * STWO_PRIME
232+
- coordinates[2] * denom_inverse_r,
233+
2 * STWO_PRIME * STWO_PRIME
234+
- coordinates[2] * denom_inverse_i
235+
- coordinates[3] * denom_inverse_r,
236+
]))
237+
}
238+
239+
/// Computes the division of two QM31 elements in reduced form.
240+
/// Returns an error if the input is zero.
241+
pub fn qm31_packed_reduced_div(felt1: Felt252, felt2: Felt252) -> Result<Felt252, MathError> {
242+
let felt2_inv = qm31_packed_reduced_inv(felt2)?;
243+
qm31_packed_reduced_mul(felt1, felt2_inv)
244+
}
245+
69246
///Returns the integer square root of the nonnegative integer n.
70247
///This is the floor of the exact square root of n.
71248
///Unlike math.sqrt(), this function doesn't have rounding error issues.
@@ -946,6 +1123,97 @@ mod tests {
9461123
)
9471124
}
9481125

1126+
#[test]
1127+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
1128+
fn qm31_packed_reduced_add_test() {
1129+
let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890];
1130+
let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988];
1131+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1132+
let y = qm31_coordinates_to_packed_reduced(y_coordinates);
1133+
let res = qm31_packed_reduced_add(x, y).unwrap();
1134+
let res_coordinates = qm31_packed_reduced_read_coordinates(res);
1135+
assert_eq!(
1136+
res_coordinates,
1137+
Ok([
1138+
(1414213562 + 1234567890) % STWO_PRIME,
1139+
(1732050807 + 1414213562) % STWO_PRIME,
1140+
(1618033988 + 1732050807) % STWO_PRIME,
1141+
(1234567890 + 1618033988) % STWO_PRIME,
1142+
])
1143+
);
1144+
}
1145+
1146+
#[test]
1147+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
1148+
fn qm31_packed_reduced_sub_test() {
1149+
let x_coordinates = [
1150+
(1414213562 + 1234567890) % STWO_PRIME,
1151+
(1732050807 + 1414213562) % STWO_PRIME,
1152+
(1618033988 + 1732050807) % STWO_PRIME,
1153+
(1234567890 + 1618033988) % STWO_PRIME,
1154+
];
1155+
let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890];
1156+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1157+
let y = qm31_coordinates_to_packed_reduced(y_coordinates);
1158+
let res = qm31_packed_reduced_sub(x, y).unwrap();
1159+
let res_coordinates = qm31_packed_reduced_read_coordinates(res);
1160+
assert_eq!(
1161+
res_coordinates,
1162+
Ok([1234567890, 1414213562, 1732050807, 1618033988])
1163+
);
1164+
}
1165+
1166+
#[test]
1167+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
1168+
fn qm31_packed_reduced_mul_test() {
1169+
let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890];
1170+
let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481];
1171+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1172+
let y = qm31_coordinates_to_packed_reduced(y_coordinates);
1173+
let res = qm31_packed_reduced_mul(x, y).unwrap();
1174+
let res_coordinates = qm31_packed_reduced_read_coordinates(res);
1175+
assert_eq!(
1176+
res_coordinates,
1177+
Ok([947980980, 1510986506, 623360030, 1260310989])
1178+
);
1179+
}
1180+
1181+
#[test]
1182+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
1183+
fn qm31_packed_reduced_inv_test() {
1184+
let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481];
1185+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1186+
let res = qm31_packed_reduced_inv(x).unwrap();
1187+
assert_eq!(qm31_packed_reduced_mul(x, res), Ok(Felt252::from(1)));
1188+
1189+
let x_coordinates = [1, 2, 3, 4];
1190+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1191+
let res = qm31_packed_reduced_inv(x).unwrap();
1192+
assert_eq!(qm31_packed_reduced_mul(x, res), Ok(Felt252::from(1)));
1193+
1194+
let x_coordinates = [1749652895, 834624081, 1930174752, 2063872165];
1195+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1196+
let res = qm31_packed_reduced_inv(x).unwrap();
1197+
assert_eq!(qm31_packed_reduced_mul(x, res), Ok(Felt252::from(1)));
1198+
}
1199+
1200+
#[test]
1201+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
1202+
fn qm31_packed_reduced_div_test() {
1203+
let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481];
1204+
let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890];
1205+
let xy_coordinates = [947980980, 1510986506, 623360030, 1260310989];
1206+
let x = qm31_coordinates_to_packed_reduced(x_coordinates);
1207+
let y = qm31_coordinates_to_packed_reduced(y_coordinates);
1208+
let xy = qm31_coordinates_to_packed_reduced(xy_coordinates);
1209+
1210+
let res = qm31_packed_reduced_div(xy, y).unwrap();
1211+
assert_eq!(res, x);
1212+
1213+
let res = qm31_packed_reduced_div(xy, x).unwrap();
1214+
assert_eq!(res, y);
1215+
}
1216+
9491217
#[cfg(feature = "std")]
9501218
proptest! {
9511219
#[test]

vm/src/types/errors/math_errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ pub enum MathError {
6565
"Operation failed: divmod({}, {}, {}), igcdex({}, {}) != 1 ", (*.0).0, (*.0).1, (*.0).2, (*.0).1, (*.0).2
6666
)]
6767
DivModIgcdexNotZero(Box<(BigInt, BigInt, BigInt)>),
68+
#[error("Numbers over 144 bit cannot be packed QM31 elements: {0})")]
69+
QM31UpackingError(Box<Felt252>),
70+
#[error("Number is not a packing of a QM31 in reduced form: {0})")]
71+
QM31UnreducedError(Box<Felt252>),
6872
}
6973

7074
#[cfg(test)]

0 commit comments

Comments
 (0)