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