@@ -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,182 @@ 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+ 5 * STWO_PRIME * STWO_PRIME + coordinates1[ 0 ] * coordinates2[ 0 ]
155+ - coordinates1[ 1 ] * coordinates2[ 1 ]
156+ + 2 * coordinates1[ 2 ] * coordinates2[ 2 ]
157+ - 2 * coordinates1[ 3 ] * coordinates2[ 3 ]
158+ - coordinates1[ 2 ] * coordinates2[ 3 ]
159+ - coordinates1[ 3 ] * coordinates2[ 2 ] ,
160+ STWO_PRIME * STWO_PRIME
161+ + coordinates1[ 0 ] * coordinates2[ 1 ]
162+ + coordinates2[ 0 ] * coordinates1[ 1 ]
163+ + 2 * ( coordinates1[ 2 ] * coordinates2[ 3 ] + coordinates1[ 3 ] * coordinates2[ 2 ] )
164+ + coordinates1[ 2 ] * coordinates2[ 2 ]
165+ - coordinates1[ 3 ] * coordinates2[ 3 ] ,
166+ 2 * STWO_PRIME * STWO_PRIME + coordinates1[ 0 ] * coordinates2[ 2 ]
167+ - coordinates1[ 1 ] * coordinates2[ 3 ]
168+ + coordinates1[ 2 ] * coordinates2[ 0 ]
169+ - coordinates1[ 3 ] * coordinates2[ 1 ] ,
170+ coordinates1[ 0 ] * coordinates2[ 3 ]
171+ + coordinates1[ 1 ] * coordinates2[ 2 ]
172+ + coordinates1[ 2 ] * coordinates2[ 1 ]
173+ + coordinates1[ 3 ] * coordinates2[ 0 ] ,
174+ ] ;
175+ Ok ( qm31_coordinates_to_packed_reduced (
176+ result_unreduced_coordinates,
177+ ) )
178+ }
179+
180+ /// Computes `v^(STWO_PRIME-2) modulo STWO_PRIME`.
181+ pub fn pow2147483645 ( v : u128 ) -> u128 {
182+ let t0 = ( sqn ( v, 2 ) * v) % STWO_PRIME ;
183+ let t1 = ( sqn ( t0, 1 ) * t0) % STWO_PRIME ;
184+ let t2 = ( sqn ( t1, 3 ) * t0) % STWO_PRIME ;
185+ let t3 = ( sqn ( t2, 1 ) * t0) % STWO_PRIME ;
186+ let t4 = ( sqn ( t3, 8 ) * t3) % STWO_PRIME ;
187+ let t5 = ( sqn ( t4, 8 ) * t3) % STWO_PRIME ;
188+ ( sqn ( t5, 7 ) * t2) % STWO_PRIME
189+ }
190+
191+ /// Computes `v^(2^n) modulo STWO_PRIME`.
192+ fn sqn ( v : u128 , n : usize ) -> u128 {
193+ let mut u = v;
194+ for _ in 0 ..n {
195+ u = ( u * u) % STWO_PRIME ;
196+ }
197+ u
198+ }
199+
200+ /// Computes the inverse of a QM31 element in reduced form.
201+ /// Returns an error if the denominator is zero or either operand is not reduced.
202+ pub fn qm31_packed_reduced_inv ( felt : Felt252 ) -> Result < Felt252 , MathError > {
203+ if felt. is_zero ( ) {
204+ return Err ( MathError :: DividedByZero ) ;
205+ }
206+ let coordinates = qm31_packed_reduced_read_coordinates ( felt) ?;
207+
208+ let b2_r = ( coordinates[ 2 ] * coordinates[ 2 ] + STWO_PRIME * STWO_PRIME
209+ - coordinates[ 3 ] * coordinates[ 3 ] )
210+ % STWO_PRIME ;
211+ let b2_i = ( 2 * coordinates[ 2 ] * coordinates[ 3 ] ) % STWO_PRIME ;
212+
213+ let denom_r = ( coordinates[ 0 ] * coordinates[ 0 ] + STWO_PRIME * STWO_PRIME
214+ - coordinates[ 1 ] * coordinates[ 1 ]
215+ + 2 * STWO_PRIME
216+ - 2 * b2_r
217+ + b2_i)
218+ % STWO_PRIME ;
219+ let denom_i =
220+ ( 2 * coordinates[ 0 ] * coordinates[ 1 ] + 3 * STWO_PRIME - 2 * b2_i - b2_r) % STWO_PRIME ;
221+
222+ let denom_norm_squared = ( denom_r * denom_r + denom_i * denom_i) % STWO_PRIME ;
223+ let denom_norm_inverse_squared = pow2147483645 ( denom_norm_squared) ;
224+
225+ let denom_inverse_r = ( denom_r * denom_norm_inverse_squared) % STWO_PRIME ;
226+ let denom_inverse_i = ( ( STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME ;
227+
228+ Ok ( qm31_coordinates_to_packed_reduced ( [
229+ coordinates[ 0 ] * denom_inverse_r + STWO_PRIME * STWO_PRIME
230+ - coordinates[ 1 ] * denom_inverse_i,
231+ coordinates[ 0 ] * denom_inverse_i + coordinates[ 1 ] * denom_inverse_r,
232+ coordinates[ 3 ] * denom_inverse_i + STWO_PRIME * STWO_PRIME
233+ - coordinates[ 2 ] * denom_inverse_r,
234+ 2 * STWO_PRIME * STWO_PRIME
235+ - coordinates[ 2 ] * denom_inverse_i
236+ - coordinates[ 3 ] * denom_inverse_r,
237+ ] ) )
238+ }
239+
240+ /// Computes the division of two QM31 elements in reduced form.
241+ /// Returns an error if the input is zero.
242+ pub fn qm31_packed_reduced_div ( felt1 : Felt252 , felt2 : Felt252 ) -> Result < Felt252 , MathError > {
243+ let felt2_inv = qm31_packed_reduced_inv ( felt2) ?;
244+ qm31_packed_reduced_mul ( felt1, felt2_inv)
245+ }
246+
69247///Returns the integer square root of the nonnegative integer n.
70248///This is the floor of the exact square root of n.
71249///Unlike math.sqrt(), this function doesn't have rounding error issues.
@@ -946,6 +1124,180 @@ mod tests {
9461124 )
9471125 }
9481126
1127+ #[ test]
1128+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1129+ fn qm31_packed_reduced_read_coordinates_over_144_bits ( ) {
1130+ let mut felt_bytes = [ 0u8 ; 32 ] ;
1131+ felt_bytes[ 18 ] = 1 ;
1132+ let felt = Felt252 :: from_bytes_le ( & felt_bytes) ;
1133+ assert_matches ! (
1134+ qm31_packed_reduced_read_coordinates( felt) ,
1135+ Err ( MathError :: QM31UpackingError ( bx) ) if * bx == felt
1136+ ) ;
1137+ }
1138+
1139+ #[ test]
1140+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1141+ fn qm31_packed_reduced_read_coordinates_unreduced ( ) {
1142+ let mut felt_bytes = [ 0u8 ; 32 ] ;
1143+ felt_bytes[ 0 ] = 0xff ;
1144+ felt_bytes[ 1 ] = 0xff ;
1145+ felt_bytes[ 2 ] = 0xff ;
1146+ felt_bytes[ 3 ] = ( 1 << 7 ) - 1 ;
1147+ let felt = Felt252 :: from_bytes_le ( & felt_bytes) ;
1148+ assert_matches ! (
1149+ qm31_packed_reduced_read_coordinates( felt) ,
1150+ Err ( MathError :: QM31UnreducedError ( bx) ) if * bx == felt
1151+ ) ;
1152+ }
1153+
1154+ #[ test]
1155+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1156+ fn qm31_packed_reduced_add_test ( ) {
1157+ let x_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1158+ let y_coordinates = [ 1234567890 , 1414213562 , 1732050807 , 1618033988 ] ;
1159+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1160+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1161+ let res = qm31_packed_reduced_add ( x, y) . unwrap ( ) ;
1162+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1163+ assert_eq ! (
1164+ res_coordinates,
1165+ Ok ( [
1166+ ( 1414213562 + 1234567890 ) % STWO_PRIME ,
1167+ ( 1732050807 + 1414213562 ) % STWO_PRIME ,
1168+ ( 1618033988 + 1732050807 ) % STWO_PRIME ,
1169+ ( 1234567890 + 1618033988 ) % STWO_PRIME ,
1170+ ] )
1171+ ) ;
1172+ }
1173+
1174+ #[ test]
1175+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1176+ fn qm31_packed_reduced_neg_test ( ) {
1177+ let x_coordinates = [ 1749652895 , 834624081 , 1930174752 , 2063872165 ] ;
1178+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1179+ let res = qm31_packed_reduced_neg ( x) . unwrap ( ) ;
1180+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1181+ assert_eq ! (
1182+ res_coordinates,
1183+ Ok ( [
1184+ STWO_PRIME - x_coordinates[ 0 ] ,
1185+ STWO_PRIME - x_coordinates[ 1 ] ,
1186+ STWO_PRIME - x_coordinates[ 2 ] ,
1187+ STWO_PRIME - x_coordinates[ 3 ]
1188+ ] )
1189+ ) ;
1190+ }
1191+
1192+ #[ test]
1193+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1194+ fn qm31_packed_reduced_sub_test ( ) {
1195+ let x_coordinates = [
1196+ ( 1414213562 + 1234567890 ) % STWO_PRIME ,
1197+ ( 1732050807 + 1414213562 ) % STWO_PRIME ,
1198+ ( 1618033988 + 1732050807 ) % STWO_PRIME ,
1199+ ( 1234567890 + 1618033988 ) % STWO_PRIME ,
1200+ ] ;
1201+ let y_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1202+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1203+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1204+ let res = qm31_packed_reduced_sub ( x, y) . unwrap ( ) ;
1205+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1206+ assert_eq ! (
1207+ res_coordinates,
1208+ Ok ( [ 1234567890 , 1414213562 , 1732050807 , 1618033988 ] )
1209+ ) ;
1210+ }
1211+
1212+ #[ test]
1213+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1214+ fn qm31_packed_reduced_mul_test ( ) {
1215+ let x_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1216+ let y_coordinates = [ 1259921049 , 1442249570 , 1847759065 , 2094551481 ] ;
1217+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1218+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1219+ let res = qm31_packed_reduced_mul ( x, y) . unwrap ( ) ;
1220+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1221+ assert_eq ! (
1222+ res_coordinates,
1223+ Ok ( [ 947980980 , 1510986506 , 623360030 , 1260310989 ] )
1224+ ) ;
1225+ }
1226+
1227+ #[ test]
1228+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1229+ fn qm31_packed_reduced_inv_test ( ) {
1230+ let x_coordinates = [ 1259921049 , 1442249570 , 1847759065 , 2094551481 ] ;
1231+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1232+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1233+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1234+
1235+ let x_coordinates = [ 1 , 2 , 3 , 4 ] ;
1236+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1237+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1238+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1239+
1240+ let x_coordinates = [ 1749652895 , 834624081 , 1930174752 , 2063872165 ] ;
1241+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1242+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1243+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1244+ }
1245+
1246+ #[ test]
1247+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1248+ fn qm31_packed_reduced_inv_extensive_test ( ) {
1249+ let mut rng = SmallRng :: seed_from_u64 ( 11480028852697973135 ) ;
1250+ let configurations = vec ! [ "zero" , "one" , "minus_one" , "random" ] ;
1251+ let mut cartesian_product: Vec < [ & str ; 4 ] > = vec ! [ ] ;
1252+ for & a in & configurations {
1253+ for & b in & configurations {
1254+ for & c in & configurations {
1255+ for & d in & configurations {
1256+ cartesian_product. push ( [ a, b, c, d] ) ;
1257+ }
1258+ }
1259+ }
1260+ }
1261+
1262+ for test_case in cartesian_product {
1263+ let x_coordinates: [ u128 ; 4 ] = test_case
1264+ . iter ( )
1265+ . map ( |& x| match x {
1266+ "zero" => 0 ,
1267+ "one" => 1 ,
1268+ "minus_one" => STWO_PRIME - 1 ,
1269+ "random" => rng. gen_range ( 0 ..STWO_PRIME ) ,
1270+ _ => unreachable ! ( ) ,
1271+ } )
1272+ . collect :: < Vec < u128 > > ( )
1273+ . try_into ( )
1274+ . unwrap ( ) ;
1275+ if x_coordinates == [ 0 , 0 , 0 , 0 ] {
1276+ continue ;
1277+ }
1278+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1279+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1280+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1281+ }
1282+ }
1283+
1284+ #[ test]
1285+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1286+ fn qm31_packed_reduced_div_test ( ) {
1287+ let x_coordinates = [ 1259921049 , 1442249570 , 1847759065 , 2094551481 ] ;
1288+ let y_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1289+ let xy_coordinates = [ 947980980 , 1510986506 , 623360030 , 1260310989 ] ;
1290+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1291+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1292+ let xy = qm31_coordinates_to_packed_reduced ( xy_coordinates) ;
1293+
1294+ let res = qm31_packed_reduced_div ( xy, y) . unwrap ( ) ;
1295+ assert_eq ! ( res, x) ;
1296+
1297+ let res = qm31_packed_reduced_div ( xy, x) . unwrap ( ) ;
1298+ assert_eq ! ( res, y) ;
1299+ }
1300+
9491301 #[ cfg( feature = "std" ) ]
9501302 proptest ! {
9511303 #[ test]
0 commit comments