diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bfcb15..d626404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added `map_to_point` and `unmap_from_point` methods [#149] + ## [0.15.0] - 2025-02-05 ### Changed @@ -236,6 +240,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Initial fork from [`zkcrypto/jubjub`] +[#149]: https://github.com/dusk-network/jubjub/issues/149 [#143]: https://github.com/dusk-network/jubjub/issues/143 [#137]: https://github.com/dusk-network/jubjub/issues/137 [#135]: https://github.com/dusk-network/jubjub/issues/135 diff --git a/benches/fq_bench.rs b/benches/fq_bench.rs index 80ab7b4..ed79d14 100644 --- a/benches/fq_bench.rs +++ b/benches/fq_bench.rs @@ -1,5 +1,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use dusk_jubjub::*; +use ff::Field; fn bench_add_assign(c: &mut Criterion) { let mut n = Fq::one(); diff --git a/src/dusk.rs b/src/dusk.rs index 84d2ead..c4549da 100644 --- a/src/dusk.rs +++ b/src/dusk.rs @@ -276,6 +276,69 @@ impl JubJubExtended { } } + /// Method that maps a [`u64`] input value to a [`JubJubExtended`] point + /// of the curve. This is a bidirectional operation, meaning + /// that the operation can be reverted by using the 'unmap_from_point()' + /// function. + /// + /// This is a probabilistic method, meaning that trying to find a correct + /// map can require several tries, until finding a real point on the curve, + /// that lies also on the correct subgroup. + pub fn map_to_point(input: &u64) -> Self { + let input = input.to_le_bytes(); + + // we take the generator's y coodinate as our starting point + let mut y_coordinate = GENERATOR.get_v(); + + // this will be the bytes representation of our target point + let mut point_bytes = y_coordinate.to_bytes(); + + // we craft a point = (y_coordinate[31..8] || u64_input_value) + point_bytes[..u64::SIZE].copy_from_slice(&input); + y_coordinate = BlsScalar::from_bytes(&point_bytes).unwrap(); + + // the value we'll add on each iteration: + // 0x0000000000000000000000000000000000000000000000010000000000000000 + let adder = BlsScalar::from(u64::MAX) + BlsScalar::one(); + + // we set a maximum number of iterations to avoid an + // 'in-practice' infinte loop + for _ in 0..u64::MAX { + // check if we hit a point on the curve + if let Ok(point) = + >::from_bytes(&point_bytes) + { + // check if this point is part of the correct subgroup and not + // the identity + if point.is_prime_order().into() { + return point.into(); + } + } + + // if the previous step doesn't succeed, we keep trying by + // checking all possible combinations for all the 24 bytes + // not belonging to the input value side of the vector + // + // Notice that we we just try out the upper bound of the curve, + // we don't care about negative points since anyways + // we have set a maximum number of tries + y_coordinate += adder; + point_bytes = y_coordinate.to_bytes(); + } + + panic!("No point is likely to be found soon enough."); + } + + /// Method that unmaps a [`JubJubExtended`] point of the curve (created + /// using the 'map_to_point()' function) to its original [`u64`] value. + pub fn unmap_from_point(self) -> u64 { + let point_bytes: [u8; u64::SIZE] = JubJubAffine::from(self).to_bytes() + [..u64::SIZE] + .try_into() + .unwrap(); + u64::from_le_bytes(point_bytes) + } + /// Returns true if this point is on the curve. This should always return /// true unless an "unchecked" API was used. pub fn is_on_curve(&self) -> Choice { @@ -289,6 +352,22 @@ impl JubJubExtended { } } +#[test] +fn test_map_to_point() { + use rand::Rng; + + let mut rng = rand::thread_rng(); + + // Test several random values + for _ in 0..500 { + let value: u64 = rng.gen(); + let point = JubJubExtended::map_to_point(&value); + let unmapped_value = point.unmap_from_point(); + + assert_eq!(value, unmapped_value); + } +} + #[test] fn test_affine_point_generator_has_order_p() { assert_eq!(GENERATOR.is_prime_order().unwrap_u8(), 1);