/ console / program / src / data / literal / cast_lossy / scalar.rs
scalar.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 Scalar<E> {
 22      /// Casts a `Scalar` to an `Address`.
 23      ///
 24      /// This operation converts the scalar into a field element, and then attempts to recover
 25      /// the group element 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          let field: Field<E> = self.cast_lossy();
 30          field.cast_lossy()
 31      }
 32  }
 33  
 34  impl<E: Environment> CastLossy<Boolean<E>> for Scalar<E> {
 35      /// Casts a `Scalar` to a `Boolean`, with lossy truncation.
 36      /// This operation returns the least significant bit of the field.
 37      #[inline]
 38      fn cast_lossy(&self) -> Boolean<E> {
 39          let bits_le = self.to_bits_le();
 40          debug_assert!(!bits_le.is_empty(), "An integer must have at least one bit");
 41          Boolean::new(bits_le[0])
 42      }
 43  }
 44  
 45  impl<E: Environment> CastLossy<Group<E>> for Scalar<E> {
 46      /// Casts a `Scalar` to a `Group`.
 47      ///
 48      /// This operation converts the scalar into a field element, and then attempts to recover
 49      /// the group element. See the documentation of `Field::cast_lossy` on the `Group` type
 50      /// for more details.
 51      #[inline]
 52      fn cast_lossy(&self) -> Group<E> {
 53          let field: Field<E> = self.cast_lossy();
 54          field.cast_lossy()
 55      }
 56  }
 57  
 58  impl<E: Environment> CastLossy<Field<E>> for Scalar<E> {
 59      /// Casts a `Scalar` to a `Field`.
 60      /// This operation is **always** lossless.
 61      #[inline]
 62      fn cast_lossy(&self) -> Field<E> {
 63          let result = self.to_field();
 64          debug_assert!(result.is_ok(), "A scalar should always be able to be converted to a field");
 65          result.unwrap()
 66      }
 67  }
 68  
 69  impl<E: Environment, I: IntegerType> CastLossy<Integer<E, I>> for Scalar<E> {
 70      /// Casts a `Scalar` to an `Integer`, with lossy truncation.
 71      #[inline]
 72      fn cast_lossy(&self) -> Integer<E, I> {
 73          // Note: We are reconstituting the integer from the scalar field.
 74          // This is safe as the number of bits in the integer is less than the scalar field modulus,
 75          // and thus will always fit within a single scalar field element.
 76          debug_assert!(I::BITS < Scalar::<E>::size_in_bits() as u64);
 77  
 78          // Truncate the field to the size of the integer domain.
 79          // Slicing here is safe as the base field is larger than the integer domain.
 80          let result = Integer::<E, I>::from_bits_le(&self.to_bits_le()[..usize::try_from(I::BITS).unwrap()]);
 81          debug_assert!(result.is_ok(), "A lossy integer should always be able to be constructed from scalar bits");
 82          result.unwrap()
 83      }
 84  }
 85  
 86  impl<E: Environment> CastLossy<Scalar<E>> for Scalar<E> {
 87      /// Casts a `Scalar` to a `Scalar`.
 88      /// This is an identity cast, so it is **always** lossless.
 89      #[inline]
 90      fn cast_lossy(&self) -> Scalar<E> {
 91          *self
 92      }
 93  }
 94  
 95  #[cfg(test)]
 96  mod tests {
 97      use super::*;
 98  
 99      type CurrentEnvironment = Console;
100  
101      const ITERATIONS: u64 = 10_000;
102  
103      #[test]
104      fn test_scalar_to_address() {
105          let rng = &mut TestRng::default();
106  
107          let scalar = Scalar::<CurrentEnvironment>::one();
108          let address: Address<CurrentEnvironment> = scalar.cast_lossy();
109          assert_eq!(address, Address::new(Group::generator()));
110          assert_eq!(address.to_group(), &Group::generator());
111  
112          let scalar = Scalar::<CurrentEnvironment>::zero();
113          let address: Address<CurrentEnvironment> = scalar.cast_lossy();
114          assert_eq!(address, Address::zero());
115          assert_eq!(address.to_group(), &Group::zero());
116  
117          for _ in 0..ITERATIONS {
118              // Sample a random scalar.
119              let scalar = Scalar::<CurrentEnvironment>::rand(rng);
120              // Perform the operation.
121              let candidate = scalar.cast_lossy();
122              // Compare the result against the group element. (This is the most we can do.)
123              let expected: Group<CurrentEnvironment> = scalar.cast_lossy();
124              assert_eq!(Address::new(expected), candidate);
125          }
126      }
127  
128      #[test]
129      fn test_scalar_to_boolean() {
130          let rng = &mut TestRng::default();
131  
132          let scalar = Scalar::<CurrentEnvironment>::one();
133          let boolean: Boolean<CurrentEnvironment> = scalar.cast_lossy();
134          assert_eq!(boolean, Boolean::new(true));
135  
136          let scalar = Scalar::<CurrentEnvironment>::zero();
137          let boolean: Boolean<CurrentEnvironment> = scalar.cast_lossy();
138          assert_eq!(boolean, Boolean::new(false));
139  
140          for _ in 0..ITERATIONS {
141              // Sample a random scalar.
142              let scalar = Scalar::<CurrentEnvironment>::rand(rng);
143              // Perform the operation.
144              let candidate = scalar.cast_lossy();
145              // Compare the result against the least significant bit of the scalar.
146              let expected = Boolean::new(scalar.to_bits_be().pop().unwrap());
147              assert_eq!(expected, candidate);
148          }
149      }
150  
151      #[test]
152      fn test_scalar_to_field() {
153          let rng = &mut TestRng::default();
154  
155          for _ in 0..ITERATIONS {
156              // Sample a random scalar.
157              let scalar = Scalar::<CurrentEnvironment>::rand(rng);
158              // Perform the operation.
159              let candidate = scalar.cast_lossy();
160              assert_eq!(scalar.to_field().unwrap(), candidate);
161          }
162      }
163  
164      #[test]
165      fn test_scalar_to_group() {
166          let rng = &mut TestRng::default();
167  
168          let scalar = Scalar::<CurrentEnvironment>::one();
169          let group: Group<CurrentEnvironment> = scalar.cast_lossy();
170          assert_eq!(group, Group::generator());
171  
172          let scalar = Scalar::<CurrentEnvironment>::zero();
173          let group: Group<CurrentEnvironment> = scalar.cast_lossy();
174          assert_eq!(group, Group::zero());
175  
176          for _ in 0..ITERATIONS {
177              // Sample a random scalar.
178              let scalar = Scalar::<CurrentEnvironment>::rand(rng);
179              // Perform the operation.
180              let candidate: Group<CurrentEnvironment> = scalar.cast_lossy();
181              // Compare the result against the address. (This is the most we can do.)
182              let expected: Address<CurrentEnvironment> = scalar.cast_lossy();
183              assert_eq!(expected.to_group(), &candidate);
184          }
185      }
186  
187      #[test]
188      fn test_scalar_to_scalar() {
189          let rng = &mut TestRng::default();
190  
191          for _ in 0..ITERATIONS {
192              // Sample a random scalar.
193              let scalar = Scalar::<CurrentEnvironment>::rand(rng);
194              // Perform the operation.
195              let candidate: Scalar<CurrentEnvironment> = scalar.cast_lossy();
196              assert_eq!(scalar, candidate);
197          }
198      }
199  
200      macro_rules! check_scalar_to_integer {
201          ($type:ty) => {
202              let rng = &mut TestRng::default();
203  
204              let scalar = Scalar::<CurrentEnvironment>::one();
205              let integer: Integer<CurrentEnvironment, $type> = scalar.cast_lossy();
206              assert_eq!(integer, Integer::<CurrentEnvironment, $type>::one());
207  
208              let scalar = Scalar::<CurrentEnvironment>::zero();
209              let integer: Integer<CurrentEnvironment, $type> = scalar.cast_lossy();
210              assert_eq!(integer, Integer::<CurrentEnvironment, $type>::zero());
211  
212              for _ in 0..ITERATIONS {
213                  // Sample a random scalar.
214                  let scalar = Scalar::<CurrentEnvironment>::rand(rng);
215                  // Perform the operation.
216                  let candidate: Integer<CurrentEnvironment, $type> = scalar.cast_lossy();
217                  // Compare the result against the least significant bits of the scalar.
218                  let expected = Integer::<CurrentEnvironment, $type>::from_bits_le(
219                      &scalar.to_bits_le()[..usize::try_from(<$type>::BITS).unwrap()],
220                  )
221                  .unwrap();
222                  assert_eq!(expected, candidate);
223              }
224          };
225      }
226  
227      #[test]
228      fn test_scalar_to_i8() {
229          check_scalar_to_integer!(i8);
230      }
231  
232      #[test]
233      fn test_scalar_to_i16() {
234          check_scalar_to_integer!(i16);
235      }
236  
237      #[test]
238      fn test_scalar_to_i32() {
239          check_scalar_to_integer!(i32);
240      }
241  
242      #[test]
243      fn test_scalar_to_i64() {
244          check_scalar_to_integer!(i64);
245      }
246  
247      #[test]
248      fn test_scalar_to_i128() {
249          check_scalar_to_integer!(i128);
250      }
251  
252      #[test]
253      fn test_scalar_to_u8() {
254          check_scalar_to_integer!(u8);
255      }
256  
257      #[test]
258      fn test_scalar_to_u16() {
259          check_scalar_to_integer!(u16);
260      }
261  
262      #[test]
263      fn test_scalar_to_u32() {
264          check_scalar_to_integer!(u32);
265      }
266  
267      #[test]
268      fn test_scalar_to_u64() {
269          check_scalar_to_integer!(u64);
270      }
271  
272      #[test]
273      fn test_scalar_to_u128() {
274          check_scalar_to_integer!(u128);
275      }
276  }