@@ -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]
0 commit comments