/ console / program / src / data / literal / cast_lossy / field.rs
field.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  use super::*;
 20  
 21  impl<E: Environment> CastLossy<Address<E>> for Field<E> {
 22      /// Casts a `Field` to an `Address`.
 23      ///
 24      /// This operation attempts to recover the group element from the given field,
 25      /// which is then used to construct the address. See the documentation of `Field::cast_lossy`
 26      /// on the `Group` type for more details.
 27      #[inline]
 28      fn cast_lossy(&self) -> Address<E> {
 29          // Perform a lossy cast to a group element.
 30          let group: Group<E> = self.cast_lossy();
 31          // Convert the group element to an address.
 32          Address::new(group)
 33      }
 34  }
 35  
 36  impl<E: Environment> CastLossy<Boolean<E>> for Field<E> {
 37      /// Casts a `Field` to a `Boolean`, with lossy truncation.
 38      /// This operation returns the least significant bit of the field.
 39      #[inline]
 40      fn cast_lossy(&self) -> Boolean<E> {
 41          let bits_le = self.to_bits_le();
 42          debug_assert!(!bits_le.is_empty(), "An integer must have at least one bit");
 43          Boolean::new(bits_le[0])
 44      }
 45  }
 46  
 47  impl<E: Environment> CastLossy<Field<E>> for Field<E> {
 48      /// Casts a `Field` to a `Field`.
 49      /// This is an identity cast, so it is **always** lossless.
 50      #[inline]
 51      fn cast_lossy(&self) -> Field<E> {
 52          *self
 53      }
 54  }
 55  
 56  impl<E: Environment> CastLossy<Group<E>> for Field<E> {
 57      /// Casts a `Field` to a `Group`.
 58      ///
 59      /// This operation attempts to recover the group element from the given field.
 60      ///
 61      /// If the field is a valid x-coordinate, then the group element is returned.
 62      /// If the field is not a valid x-coordinate, then if the field is the one element,
 63      /// the generator of the prime-order subgroup is returned.
 64      /// Otherwise, Elligator-2 is applied to the field element to recover a group element.
 65      #[inline]
 66      fn cast_lossy(&self) -> Group<E> {
 67          match Group::from_x_coordinate(*self) {
 68              Ok(group) => group,
 69              Err(_) => match self.is_one() {
 70                  true => Group::generator(),
 71                  false => {
 72                      // Perform Elligator-2 on the field element, to recover a group element.
 73                      let result = Elligator2::encode(self);
 74                      debug_assert!(result.is_ok(), "Elligator-2 should never fail to encode a field element");
 75                      result.unwrap().0
 76                  }
 77              },
 78          }
 79      }
 80  }
 81  
 82  impl<E: Environment, I: IntegerType> CastLossy<Integer<E, I>> for Field<E> {
 83      /// Casts a `Field` to an `Integer`, with lossy truncation.
 84      /// This operation truncates the field to an integer.
 85      #[inline]
 86      fn cast_lossy(&self) -> Integer<E, I> {
 87          Integer::from_field_lossy(self)
 88      }
 89  }
 90  
 91  impl<E: Environment> CastLossy<Scalar<E>> for Field<E> {
 92      /// Casts a `Field` to a `Scalar`, with lossy truncation.
 93      /// This operation truncates the field to a scalar.
 94      #[inline]
 95      fn cast_lossy(&self) -> Scalar<E> {
 96          Scalar::from_field_lossy(self)
 97      }
 98  }
 99  
100  #[cfg(test)]
101  mod tests {
102      use super::*;
103      use alphavm_console_network::Console;
104  
105      type CurrentEnvironment = Console;
106  
107      const ITERATIONS: u64 = 10_000;
108  
109      #[test]
110      fn test_field_to_address() {
111          let rng = &mut TestRng::default();
112  
113          let field = Field::<CurrentEnvironment>::one();
114          let address: Address<CurrentEnvironment> = field.cast_lossy();
115          assert_eq!(address, Address::new(Group::generator()));
116          assert_eq!(address.to_group(), &Group::generator());
117  
118          let field = Field::<CurrentEnvironment>::zero();
119          let address: Address<CurrentEnvironment> = field.cast_lossy();
120          assert_eq!(address, Address::zero());
121          assert_eq!(address.to_group(), &Group::zero());
122  
123          for _ in 0..ITERATIONS {
124              // Sample a random field.
125              let field = Field::<CurrentEnvironment>::rand(rng);
126              // Perform the operation.
127              let candidate: Address<CurrentEnvironment> = field.cast_lossy();
128              // Compare the result against the group element. (This is the most we can do.)
129              let expected: Group<CurrentEnvironment> = field.cast_lossy();
130              assert_eq!(Address::new(expected), candidate);
131          }
132      }
133  
134      #[test]
135      fn test_field_to_boolean() {
136          let rng = &mut TestRng::default();
137  
138          let field = Field::<CurrentEnvironment>::one();
139          let boolean: Boolean<CurrentEnvironment> = field.cast_lossy();
140          assert_eq!(boolean, Boolean::new(true));
141  
142          let field = Field::<CurrentEnvironment>::zero();
143          let boolean: Boolean<CurrentEnvironment> = field.cast_lossy();
144          assert_eq!(boolean, Boolean::new(false));
145  
146          for _ in 0..ITERATIONS {
147              // Sample a random field.
148              let field = Field::<CurrentEnvironment>::rand(rng);
149              // Perform the operation.
150              let candidate: Boolean<CurrentEnvironment> = field.cast_lossy();
151              // Compare the result against the least significant bit of the field.
152              let expected = Boolean::new(field.to_bits_be().pop().unwrap());
153              assert_eq!(expected, candidate);
154          }
155      }
156  
157      #[test]
158      fn test_field_to_field() {
159          let rng = &mut TestRng::default();
160  
161          for _ in 0..ITERATIONS {
162              // Sample a random field.
163              let field = Field::<CurrentEnvironment>::rand(rng);
164              // Perform the operation.
165              let candidate: Field<CurrentEnvironment> = field.cast_lossy();
166              assert_eq!(field, candidate);
167          }
168      }
169  
170      #[test]
171      fn test_field_to_group() {
172          let rng = &mut TestRng::default();
173  
174          let field = Field::<CurrentEnvironment>::one();
175          let group: Group<CurrentEnvironment> = field.cast_lossy();
176          assert_eq!(group, Group::generator());
177  
178          let field = Field::<CurrentEnvironment>::zero();
179          let group: Group<CurrentEnvironment> = field.cast_lossy();
180          assert_eq!(group, Group::zero());
181  
182          for _ in 0..ITERATIONS {
183              // Sample a random field.
184              let field = Field::<CurrentEnvironment>::rand(rng);
185              // Perform the operation.
186              let candidate: Group<CurrentEnvironment> = field.cast_lossy();
187              // Compare the result against the address. (This is the most we can do.)
188              let expected: Address<CurrentEnvironment> = field.cast_lossy();
189              assert_eq!(expected.to_group(), &candidate);
190          }
191      }
192  
193      #[test]
194      fn test_field_to_scalar() {
195          let rng = &mut TestRng::default();
196  
197          let field = Field::<CurrentEnvironment>::one();
198          let scalar: Scalar<CurrentEnvironment> = field.cast_lossy();
199          assert_eq!(scalar, Scalar::one());
200  
201          let field = Field::<CurrentEnvironment>::zero();
202          let scalar: Scalar<CurrentEnvironment> = field.cast_lossy();
203          assert_eq!(scalar, Scalar::zero());
204  
205          for _ in 0..ITERATIONS {
206              // Sample a random field.
207              let field = Field::<CurrentEnvironment>::rand(rng);
208              // Perform the operation.
209              let candidate: Scalar<CurrentEnvironment> = field.cast_lossy();
210              assert_eq!(Scalar::from_field_lossy(&field), candidate);
211          }
212      }
213  
214      macro_rules! check_field_to_integer {
215          ($type:ty) => {
216              let rng = &mut TestRng::default();
217  
218              let field = Field::<CurrentEnvironment>::one();
219              let integer: $type = field.cast_lossy();
220              assert_eq!(integer, <$type>::one());
221  
222              let field = Field::<CurrentEnvironment>::zero();
223              let integer: $type = field.cast_lossy();
224              assert_eq!(integer, <$type>::zero());
225  
226              for _ in 0..ITERATIONS {
227                  // Sample a random field.
228                  let field = Field::<CurrentEnvironment>::rand(rng);
229                  // Perform the operation.
230                  let candidate: $type = field.cast_lossy();
231                  assert_eq!(<$type>::from_field_lossy(&field), candidate);
232              }
233          };
234      }
235  
236      #[test]
237      fn test_field_to_i8() {
238          check_field_to_integer!(I8<CurrentEnvironment>);
239      }
240  
241      #[test]
242      fn test_field_to_i16() {
243          check_field_to_integer!(I16<CurrentEnvironment>);
244      }
245  
246      #[test]
247      fn test_field_to_i32() {
248          check_field_to_integer!(I32<CurrentEnvironment>);
249      }
250  
251      #[test]
252      fn test_field_to_i64() {
253          check_field_to_integer!(I64<CurrentEnvironment>);
254      }
255  
256      #[test]
257      fn test_field_to_i128() {
258          check_field_to_integer!(I128<CurrentEnvironment>);
259      }
260  
261      #[test]
262      fn test_field_to_u8() {
263          check_field_to_integer!(U8<CurrentEnvironment>);
264      }
265  
266      #[test]
267      fn test_field_to_u16() {
268          check_field_to_integer!(U16<CurrentEnvironment>);
269      }
270  
271      #[test]
272      fn test_field_to_u32() {
273          check_field_to_integer!(U32<CurrentEnvironment>);
274      }
275  
276      #[test]
277      fn test_field_to_u64() {
278          check_field_to_integer!(U64<CurrentEnvironment>);
279      }
280  
281      #[test]
282      fn test_field_to_u128() {
283          check_field_to_integer!(U128<CurrentEnvironment>);
284      }
285  }