snarkvm_circuit_program/data/literal/cast_lossy/
field.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<E: Environment> CastLossy<Address<E>> for Field<E> {
19    /// Casts a `Field` to an `Address`.
20    ///
21    /// This operation attempts to recover the group element from the given field,
22    /// which is then used to construct the address. See the documentation of `Field::cast_lossy`
23    /// on the `Group` type for more details.
24    #[inline]
25    fn cast_lossy(&self) -> Address<E> {
26        // Perform a lossy cast to a group element.
27        let group: Group<E> = self.cast_lossy();
28        // Convert the group element to an address.
29        Address::from_group(group)
30    }
31}
32
33impl<E: Environment> CastLossy<Boolean<E>> for Field<E> {
34    /// Casts a `Field` to a `Boolean`, with lossy truncation.
35    /// This operation returns the least significant bit of the field.
36    #[inline]
37    fn cast_lossy(&self) -> Boolean<E> {
38        let bits_le = self.to_bits_le();
39        debug_assert!(!bits_le.is_empty(), "An integer must have at least one bit");
40        bits_le[0].clone()
41    }
42}
43
44impl<E: Environment> CastLossy<Field<E>> for Field<E> {
45    /// Casts a `Field` to a `Field`.
46    /// This is an identity cast, so it is **always** lossless.
47    #[inline]
48    fn cast_lossy(&self) -> Field<E> {
49        self.clone()
50    }
51}
52
53impl<E: Environment> CastLossy<Group<E>> for Field<E> {
54    /// Casts a `Field` to a `Group`.
55    ///
56    /// This operation attempts to recover the group element from the given field.
57    ///
58    /// If the field is a valid x-coordinate, then the group element is returned.
59    /// If the field is not a valid x-coordinate, then if the field is the one element,
60    /// the generator of the prime-order subgroup is returned.
61    /// Otherwise, Elligator-2 is applied to the field element to recover a group element.
62    #[inline]
63    fn cast_lossy(&self) -> Group<E> {
64        // This method requires that an `x-coordinate` of 1 is an invalid group element.
65        // This is used by the ternary below, which uses 'is_x_one' to determine whether to return the generator.
66        debug_assert!(console::Group::from_x_coordinate(<console::Field<E::Network> as console::One>::one()).is_err());
67
68        // Attempt to find a group element with self as the x-coordinate.
69        let (point_with_x, x_is_not_in_group) = Group::from_x_coordinate_flagged(self.clone());
70
71        // Determine if the field element is zero.
72        let is_x_zero = self.is_zero();
73        // Determine if the field element is one.
74        let is_x_one = self.is_one();
75
76        // Initialize the group generator.
77        let generator = Group::generator();
78
79        // Determine the input to Elligator-2, based on the x-coordinate.
80        // If self is 0, we pass 1 to Elligator-2 instead.
81        // Note that, in this case, we won't use the result of Elligator-2,
82        // because the point (0, 1) is in the subgroup, and that is what we return.
83        let elligator_input = Field::ternary(&is_x_zero, &Field::one(), self);
84        // Perform Elligator-2 on the field element, to recover a group element.
85        let elligator_point = Elligator2::encode(&elligator_input);
86
87        // Select either the generator or the result of Elligator-2, depending on whether x is 1 or not.
88        // This is only used when x is not in the group, see below.
89        let generator_or_elligator_point = Group::ternary(&is_x_one, &generator, &elligator_point);
90
91        // Select either the group point with x or the generator or the result of Elligator-2,
92        // depending on whether x is in the group or not, and, if it is not, based on whether it is 1 or not.
93        Group::ternary(&x_is_not_in_group, &generator_or_elligator_point, &point_with_x)
94    }
95}
96
97impl<E: Environment, I: IntegerType> CastLossy<Integer<E, I>> for Field<E> {
98    /// Casts a `Field` to an `Integer`, with lossy truncation.
99    /// This operation truncates the field to an integer.
100    #[inline]
101    fn cast_lossy(&self) -> Integer<E, I> {
102        Integer::from_field_lossy(self)
103    }
104}
105
106impl<E: Environment> CastLossy<Scalar<E>> for Field<E> {
107    /// Casts a `Field` to a `Scalar`, with lossy truncation.
108    /// This operation truncates the field to a scalar.
109    #[inline]
110    fn cast_lossy(&self) -> Scalar<E> {
111        Scalar::from_field_lossy(self)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use console::CastLossy as _;
119    use console_root::{
120        network::MainnetV0,
121        prelude::{One, TestRng, Uniform, Zero},
122    };
123    use snarkvm_circuit_types::environment::{Circuit, Eject, Inject, Mode, UpdatableCount, count_is, count_less_than};
124
125    use std::fmt::Debug;
126
127    const ITERATIONS: usize = 100;
128
129    fn sample_values(
130        i: usize,
131        mode: Mode,
132        rng: &mut TestRng,
133    ) -> (console_root::types::Field<MainnetV0>, Field<Circuit>) {
134        let console_value = match i {
135            0 => console_root::types::Field::<MainnetV0>::zero(),
136            1 => console_root::types::Field::<MainnetV0>::one(),
137            _ => Uniform::rand(rng),
138        };
139        let circuit_value = Field::<Circuit>::new(mode, console_value);
140        (console_value, circuit_value)
141    }
142
143    check_cast_lossy!(cast_lossy, Field<Circuit>, console_root::types::Field::<MainnetV0>);
144
145    #[test]
146    fn test_field_to_address() {
147        check_cast_lossy::<Address<Circuit>, console_root::types::Address<MainnetV0>>(
148            Mode::Constant,
149            count_less_than!(4303, 0, 0, 0),
150        );
151        check_cast_lossy::<Address<Circuit>, console_root::types::Address<MainnetV0>>(
152            Mode::Public,
153            count_is!(2029, 0, 6745, 6750),
154        );
155        check_cast_lossy::<Address<Circuit>, console_root::types::Address<MainnetV0>>(
156            Mode::Private,
157            count_is!(2029, 0, 6745, 6750),
158        );
159    }
160
161    #[test]
162    fn test_field_to_boolean() {
163        check_cast_lossy::<Boolean<Circuit>, console_root::types::Boolean<MainnetV0>>(
164            Mode::Constant,
165            count_is!(253, 0, 0, 0),
166        );
167        check_cast_lossy::<Boolean<Circuit>, console_root::types::Boolean<MainnetV0>>(
168            Mode::Public,
169            count_is!(0, 0, 505, 507),
170        );
171        check_cast_lossy::<Boolean<Circuit>, console_root::types::Boolean<MainnetV0>>(
172            Mode::Private,
173            count_is!(0, 0, 505, 507),
174        );
175    }
176
177    #[test]
178    fn test_field_to_field() {
179        check_cast_lossy::<Field<Circuit>, console_root::types::Field<MainnetV0>>(
180            Mode::Constant,
181            count_is!(0, 0, 0, 0),
182        );
183        check_cast_lossy::<Field<Circuit>, console_root::types::Field<MainnetV0>>(Mode::Public, count_is!(0, 0, 0, 0));
184        check_cast_lossy::<Field<Circuit>, console_root::types::Field<MainnetV0>>(Mode::Private, count_is!(0, 0, 0, 0));
185    }
186
187    #[test]
188    fn test_field_to_group() {
189        check_cast_lossy::<Group<Circuit>, console_root::types::Group<MainnetV0>>(
190            Mode::Constant,
191            count_less_than!(4303, 0, 0, 0),
192        );
193        check_cast_lossy::<Group<Circuit>, console_root::types::Group<MainnetV0>>(
194            Mode::Public,
195            count_is!(2029, 0, 6745, 6750),
196        );
197        check_cast_lossy::<Group<Circuit>, console_root::types::Group<MainnetV0>>(
198            Mode::Private,
199            count_is!(2029, 0, 6745, 6750),
200        );
201    }
202
203    #[test]
204    fn test_field_to_i8() {
205        check_cast_lossy::<I8<Circuit>, console_root::types::I8<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
206        check_cast_lossy::<I8<Circuit>, console_root::types::I8<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
207        check_cast_lossy::<I8<Circuit>, console_root::types::I8<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
208    }
209
210    #[test]
211    fn test_field_to_i16() {
212        check_cast_lossy::<I16<Circuit>, console_root::types::I16<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
213        check_cast_lossy::<I16<Circuit>, console_root::types::I16<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
214        check_cast_lossy::<I16<Circuit>, console_root::types::I16<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
215    }
216
217    #[test]
218    fn test_field_to_i32() {
219        check_cast_lossy::<I32<Circuit>, console_root::types::I32<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
220        check_cast_lossy::<I32<Circuit>, console_root::types::I32<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
221        check_cast_lossy::<I32<Circuit>, console_root::types::I32<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
222    }
223
224    #[test]
225    fn test_field_to_i64() {
226        check_cast_lossy::<I64<Circuit>, console_root::types::I64<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
227        check_cast_lossy::<I64<Circuit>, console_root::types::I64<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
228        check_cast_lossy::<I64<Circuit>, console_root::types::I64<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
229    }
230
231    #[test]
232    fn test_field_to_i128() {
233        check_cast_lossy::<I128<Circuit>, console_root::types::I128<MainnetV0>>(
234            Mode::Constant,
235            count_is!(253, 0, 0, 0),
236        );
237        check_cast_lossy::<I128<Circuit>, console_root::types::I128<MainnetV0>>(
238            Mode::Public,
239            count_is!(0, 0, 505, 507),
240        );
241        check_cast_lossy::<I128<Circuit>, console_root::types::I128<MainnetV0>>(
242            Mode::Private,
243            count_is!(0, 0, 505, 507),
244        );
245    }
246
247    #[test]
248    fn test_field_to_scalar() {
249        check_cast_lossy::<Scalar<Circuit>, console_root::types::Scalar<MainnetV0>>(
250            Mode::Constant,
251            count_is!(253, 0, 0, 0),
252        );
253        check_cast_lossy::<Scalar<Circuit>, console_root::types::Scalar<MainnetV0>>(
254            Mode::Public,
255            count_is!(0, 0, 505, 507),
256        );
257        check_cast_lossy::<Scalar<Circuit>, console_root::types::Scalar<MainnetV0>>(
258            Mode::Private,
259            count_is!(0, 0, 505, 507),
260        );
261    }
262
263    #[test]
264    fn test_field_to_u8() {
265        check_cast_lossy::<U8<Circuit>, console_root::types::U8<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
266        check_cast_lossy::<U8<Circuit>, console_root::types::U8<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
267        check_cast_lossy::<U8<Circuit>, console_root::types::U8<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
268    }
269
270    #[test]
271    fn test_field_to_u16() {
272        check_cast_lossy::<U16<Circuit>, console_root::types::U16<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
273        check_cast_lossy::<U16<Circuit>, console_root::types::U16<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
274        check_cast_lossy::<U16<Circuit>, console_root::types::U16<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
275    }
276
277    #[test]
278    fn test_field_to_u32() {
279        check_cast_lossy::<U32<Circuit>, console_root::types::U32<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
280        check_cast_lossy::<U32<Circuit>, console_root::types::U32<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
281        check_cast_lossy::<U32<Circuit>, console_root::types::U32<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
282    }
283
284    #[test]
285    fn test_field_to_u64() {
286        check_cast_lossy::<U64<Circuit>, console_root::types::U64<MainnetV0>>(Mode::Constant, count_is!(253, 0, 0, 0));
287        check_cast_lossy::<U64<Circuit>, console_root::types::U64<MainnetV0>>(Mode::Public, count_is!(0, 0, 505, 507));
288        check_cast_lossy::<U64<Circuit>, console_root::types::U64<MainnetV0>>(Mode::Private, count_is!(0, 0, 505, 507));
289    }
290
291    #[test]
292    fn test_field_to_u128() {
293        check_cast_lossy::<U128<Circuit>, console_root::types::U128<MainnetV0>>(
294            Mode::Constant,
295            count_is!(253, 0, 0, 0),
296        );
297        check_cast_lossy::<U128<Circuit>, console_root::types::U128<MainnetV0>>(
298            Mode::Public,
299            count_is!(0, 0, 505, 507),
300        );
301        check_cast_lossy::<U128<Circuit>, console_root::types::U128<MainnetV0>>(
302            Mode::Private,
303            count_is!(0, 0, 505, 507),
304        );
305    }
306}