snarkvm_console_program/data/literal/cast_lossy/
scalar.rs

1// Copyright 2024 Aleo Network Foundation
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 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
31impl<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
42impl<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
55impl<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
66impl<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
83impl<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)]
93mod 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}