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 }