@@ -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,142 @@ mod tests {
9461123 )
9471124 }
9481125
1126+ #[ test]
1127+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1128+ fn qm31_packed_reduced_read_coordinates_over_144_bits ( ) {
1129+ let mut felt_bytes = [ 0u8 ; 32 ] ;
1130+ felt_bytes[ 18 ] = 1 ;
1131+ let felt = Felt252 :: from_bytes_le ( & felt_bytes) ;
1132+ assert_matches ! (
1133+ qm31_packed_reduced_read_coordinates( felt) ,
1134+ Err ( MathError :: QM31UpackingError ( bx) ) if * bx == felt
1135+ ) ;
1136+ }
1137+
1138+ #[ test]
1139+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1140+ fn qm31_packed_reduced_read_coordinates_unreduced ( ) {
1141+ let mut felt_bytes = [ 0u8 ; 32 ] ;
1142+ felt_bytes[ 0 ] = 0xff ;
1143+ felt_bytes[ 1 ] = 0xff ;
1144+ felt_bytes[ 2 ] = 0xff ;
1145+ felt_bytes[ 3 ] = ( 1 << 7 ) - 1 ;
1146+ let felt = Felt252 :: from_bytes_le ( & felt_bytes) ;
1147+ assert_matches ! (
1148+ qm31_packed_reduced_read_coordinates( felt) ,
1149+ Err ( MathError :: QM31UnreducedError ( bx) ) if * bx == felt
1150+ ) ;
1151+ }
1152+
1153+ #[ test]
1154+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1155+ fn qm31_packed_reduced_add_test ( ) {
1156+ let x_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1157+ let y_coordinates = [ 1234567890 , 1414213562 , 1732050807 , 1618033988 ] ;
1158+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1159+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1160+ let res = qm31_packed_reduced_add ( x, y) . unwrap ( ) ;
1161+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1162+ assert_eq ! (
1163+ res_coordinates,
1164+ Ok ( [
1165+ ( 1414213562 + 1234567890 ) % STWO_PRIME ,
1166+ ( 1732050807 + 1414213562 ) % STWO_PRIME ,
1167+ ( 1618033988 + 1732050807 ) % STWO_PRIME ,
1168+ ( 1234567890 + 1618033988 ) % STWO_PRIME ,
1169+ ] )
1170+ ) ;
1171+ }
1172+
1173+ #[ test]
1174+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1175+ fn qm31_packed_reduced_neg_test ( ) {
1176+ let x_coordinates = [ 1749652895 , 834624081 , 1930174752 , 2063872165 ] ;
1177+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1178+ let res = qm31_packed_reduced_neg ( x) . unwrap ( ) ;
1179+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1180+ assert_eq ! (
1181+ res_coordinates,
1182+ Ok ( [
1183+ STWO_PRIME - x_coordinates[ 0 ] ,
1184+ STWO_PRIME - x_coordinates[ 1 ] ,
1185+ STWO_PRIME - x_coordinates[ 2 ] ,
1186+ STWO_PRIME - x_coordinates[ 3 ]
1187+ ] )
1188+ ) ;
1189+ }
1190+
1191+ #[ test]
1192+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1193+ fn qm31_packed_reduced_sub_test ( ) {
1194+ let x_coordinates = [
1195+ ( 1414213562 + 1234567890 ) % STWO_PRIME ,
1196+ ( 1732050807 + 1414213562 ) % STWO_PRIME ,
1197+ ( 1618033988 + 1732050807 ) % STWO_PRIME ,
1198+ ( 1234567890 + 1618033988 ) % STWO_PRIME ,
1199+ ] ;
1200+ let y_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1201+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1202+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1203+ let res = qm31_packed_reduced_sub ( x, y) . unwrap ( ) ;
1204+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1205+ assert_eq ! (
1206+ res_coordinates,
1207+ Ok ( [ 1234567890 , 1414213562 , 1732050807 , 1618033988 ] )
1208+ ) ;
1209+ }
1210+
1211+ #[ test]
1212+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1213+ fn qm31_packed_reduced_mul_test ( ) {
1214+ let x_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1215+ let y_coordinates = [ 1259921049 , 1442249570 , 1847759065 , 2094551481 ] ;
1216+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1217+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1218+ let res = qm31_packed_reduced_mul ( x, y) . unwrap ( ) ;
1219+ let res_coordinates = qm31_packed_reduced_read_coordinates ( res) ;
1220+ assert_eq ! (
1221+ res_coordinates,
1222+ Ok ( [ 947980980 , 1510986506 , 623360030 , 1260310989 ] )
1223+ ) ;
1224+ }
1225+
1226+ #[ test]
1227+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1228+ fn qm31_packed_reduced_inv_test ( ) {
1229+ let x_coordinates = [ 1259921049 , 1442249570 , 1847759065 , 2094551481 ] ;
1230+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1231+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1232+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1233+
1234+ let x_coordinates = [ 1 , 2 , 3 , 4 ] ;
1235+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1236+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1237+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1238+
1239+ let x_coordinates = [ 1749652895 , 834624081 , 1930174752 , 2063872165 ] ;
1240+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1241+ let res = qm31_packed_reduced_inv ( x) . unwrap ( ) ;
1242+ assert_eq ! ( qm31_packed_reduced_mul( x, res) , Ok ( Felt252 :: from( 1 ) ) ) ;
1243+ }
1244+
1245+ #[ test]
1246+ #[ cfg_attr( target_arch = "wasm32" , wasm_bindgen_test) ]
1247+ fn qm31_packed_reduced_div_test ( ) {
1248+ let x_coordinates = [ 1259921049 , 1442249570 , 1847759065 , 2094551481 ] ;
1249+ let y_coordinates = [ 1414213562 , 1732050807 , 1618033988 , 1234567890 ] ;
1250+ let xy_coordinates = [ 947980980 , 1510986506 , 623360030 , 1260310989 ] ;
1251+ let x = qm31_coordinates_to_packed_reduced ( x_coordinates) ;
1252+ let y = qm31_coordinates_to_packed_reduced ( y_coordinates) ;
1253+ let xy = qm31_coordinates_to_packed_reduced ( xy_coordinates) ;
1254+
1255+ let res = qm31_packed_reduced_div ( xy, y) . unwrap ( ) ;
1256+ assert_eq ! ( res, x) ;
1257+
1258+ let res = qm31_packed_reduced_div ( xy, x) . unwrap ( ) ;
1259+ assert_eq ! ( res, y) ;
1260+ }
1261+
9491262 #[ cfg( feature = "std" ) ]
9501263 proptest ! {
9511264 #[ test]
0 commit comments