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 }