Skip to main content

ark_models_ext/
transmute.rs

1//! Zero-cost transmutation between curve points parameterized by compatible configs.
2
3use ark_ec::{short_weierstrass as sw, twisted_edwards as te, CurveConfig};
4use core::mem::{align_of, size_of};
5
6/// Marker: `Self` and `T` are curve configs for the same curve with identical field types.
7///
8/// `BaseField` and `ScalarField` equality is enforced by the type system. Because point
9/// types (e.g. `sw::Affine<C>`) are generic structs whose fields depend only on these
10/// associated types, two instantiations with identical field types have identical layouts.
11///
12/// Strictly speaking, `#[repr(Rust)]` does not formally guarantee layout equivalence
13/// across monomorphizations, but in practice rustc lays out structs deterministically
14/// based on their field types. The compile-time size and alignment assertions in the
15/// transmute helpers provide an additional safety net.
16pub trait CompatibleConfig<T>: CurveConfig
17where
18    T: CurveConfig<BaseField = Self::BaseField, ScalarField = Self::ScalarField>,
19{
20}
21
22/// Zero-cost owned value transmutation.
23pub trait TransmuteFrom<T>: Sized {
24    fn transmute_from(t: T) -> Self;
25}
26
27/// Reverse of [`TransmuteFrom`], for ergonomics.
28pub trait TransmuteInto<U>: Sized {
29    fn transmute_into(self) -> U;
30}
31
32impl<T, U: TransmuteFrom<T>> TransmuteInto<U> for T {
33    fn transmute_into(self) -> U {
34        U::transmute_from(self)
35    }
36}
37
38/// Zero-cost reference/slice transmutation.
39pub trait TransmuteRef<T: ?Sized> {
40    fn transmute_ref(&self) -> &T;
41}
42
43/// Compile-time assertion that `S` and `D` have identical size and alignment.
44const fn assert_layout_compatible<S, D>() {
45    assert!(size_of::<S>() == size_of::<D>());
46    assert!(align_of::<S>() == align_of::<D>());
47}
48
49/// Reinterpret an owned value of type `S` as type `D`, with a compile-time layout check.
50fn transmute_value<S, D>(src: S) -> D {
51    const { assert_layout_compatible::<S, D>() }
52    let src = core::mem::ManuallyDrop::new(src);
53    unsafe { core::ptr::read(&*src as *const S as *const D) }
54}
55
56/// Reinterpret a reference from `&S` to `&D`, with a compile-time layout check.
57fn transmute_ref<S, D>(src: &S) -> &D {
58    const { assert_layout_compatible::<S, D>() }
59    unsafe { &*(src as *const S as *const D) }
60}
61
62/// Reinterpret a slice `&[S]` as `&[D]`, with a compile-time element layout check.
63fn transmute_slice<S, D>(src: &[S]) -> &[D] {
64    const { assert_layout_compatible::<S, D>() }
65    unsafe { core::slice::from_raw_parts(src.as_ptr() as *const D, src.len()) }
66}
67
68// --- TransmuteFrom impls (owned values) ---
69//
70// Bound: `D: CompatibleConfig<S>` — the destination config declares compatibility
71// with the source config. This covers the ark-to-ext direction used by default
72// CurveHooks implementations.
73
74impl<S, D> TransmuteFrom<te::Projective<S>> for te::Projective<D>
75where
76    D: te::TECurveConfig + CompatibleConfig<S>,
77    S: te::TECurveConfig<BaseField = D::BaseField, ScalarField = D::ScalarField>,
78{
79    fn transmute_from(t: te::Projective<S>) -> Self {
80        transmute_value(t)
81    }
82}
83
84impl<S, D> TransmuteFrom<te::Affine<S>> for te::Affine<D>
85where
86    D: te::TECurveConfig + CompatibleConfig<S>,
87    S: te::TECurveConfig<BaseField = D::BaseField, ScalarField = D::ScalarField>,
88{
89    fn transmute_from(t: te::Affine<S>) -> Self {
90        transmute_value(t)
91    }
92}
93
94impl<S, D> TransmuteFrom<sw::Projective<S>> for sw::Projective<D>
95where
96    D: sw::SWCurveConfig + CompatibleConfig<S>,
97    S: sw::SWCurveConfig<BaseField = D::BaseField, ScalarField = D::ScalarField>,
98{
99    fn transmute_from(t: sw::Projective<S>) -> Self {
100        transmute_value(t)
101    }
102}
103
104impl<S, D> TransmuteFrom<sw::Affine<S>> for sw::Affine<D>
105where
106    D: sw::SWCurveConfig + CompatibleConfig<S>,
107    S: sw::SWCurveConfig<BaseField = D::BaseField, ScalarField = D::ScalarField>,
108{
109    fn transmute_from(t: sw::Affine<S>) -> Self {
110        transmute_value(t)
111    }
112}
113
114// --- TransmuteRef impls (references) ---
115//
116// Bound: `S: CompatibleConfig<D>` — the source config declares compatibility with
117// the destination config. This covers the ext-to-ark direction used for input
118// reinterpretation in default CurveHooks implementations.
119
120impl<S, D> TransmuteRef<te::Projective<D>> for te::Projective<S>
121where
122    S: te::TECurveConfig + CompatibleConfig<D>,
123    D: te::TECurveConfig<BaseField = S::BaseField, ScalarField = S::ScalarField>,
124{
125    fn transmute_ref(&self) -> &te::Projective<D> {
126        transmute_ref(self)
127    }
128}
129
130impl<S, D> TransmuteRef<sw::Projective<D>> for sw::Projective<S>
131where
132    S: sw::SWCurveConfig + CompatibleConfig<D>,
133    D: sw::SWCurveConfig<BaseField = S::BaseField, ScalarField = S::ScalarField>,
134{
135    fn transmute_ref(&self) -> &sw::Projective<D> {
136        transmute_ref(self)
137    }
138}
139
140impl<S, D> TransmuteRef<sw::Affine<D>> for sw::Affine<S>
141where
142    S: sw::SWCurveConfig + CompatibleConfig<D>,
143    D: sw::SWCurveConfig<BaseField = S::BaseField, ScalarField = S::ScalarField>,
144{
145    fn transmute_ref(&self) -> &sw::Affine<D> {
146        transmute_ref(self)
147    }
148}
149
150impl<S, D> TransmuteRef<te::Affine<D>> for te::Affine<S>
151where
152    S: te::TECurveConfig + CompatibleConfig<D>,
153    D: te::TECurveConfig<BaseField = S::BaseField, ScalarField = S::ScalarField>,
154{
155    fn transmute_ref(&self) -> &te::Affine<D> {
156        transmute_ref(self)
157    }
158}
159
160// --- TransmuteRef impls (slices) ---
161
162impl<S, D> TransmuteRef<[te::Affine<D>]> for [te::Affine<S>]
163where
164    S: te::TECurveConfig + CompatibleConfig<D>,
165    D: te::TECurveConfig<BaseField = S::BaseField, ScalarField = S::ScalarField>,
166{
167    fn transmute_ref(&self) -> &[te::Affine<D>] {
168        transmute_slice(self)
169    }
170}
171
172impl<S, D> TransmuteRef<[sw::Affine<D>]> for [sw::Affine<S>]
173where
174    S: sw::SWCurveConfig + CompatibleConfig<D>,
175    D: sw::SWCurveConfig<BaseField = S::BaseField, ScalarField = S::ScalarField>,
176{
177    fn transmute_ref(&self) -> &[sw::Affine<D>] {
178        transmute_slice(self)
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use ark_std::{test_rng, UniformRand};
186
187    fn rand_point<T: UniformRand>() -> T {
188        T::rand(&mut test_rng())
189    }
190
191    fn rand_points<T: UniformRand>(n: usize) -> Vec<T> {
192        let rng = &mut test_rng();
193        (0..n).map(|_| T::rand(rng)).collect()
194    }
195
196    // We create minimal config wrappers that mirror upstream ark configs,
197    // then verify that random points survive transmute roundtrips.
198
199    mod sw_test {
200        use super::*;
201        use ark_bls12_381::g1::Config as ArkG1Config;
202
203        #[derive(Clone, Copy)]
204        struct ExtConfig;
205
206        impl CurveConfig for ExtConfig {
207            type BaseField = <ArkG1Config as CurveConfig>::BaseField;
208            type ScalarField = <ArkG1Config as CurveConfig>::ScalarField;
209            const COFACTOR: &'static [u64] = <ArkG1Config as CurveConfig>::COFACTOR;
210            const COFACTOR_INV: Self::ScalarField = <ArkG1Config as CurveConfig>::COFACTOR_INV;
211        }
212
213        impl sw::SWCurveConfig for ExtConfig {
214            const COEFF_A: Self::BaseField = <ArkG1Config as sw::SWCurveConfig>::COEFF_A;
215            const COEFF_B: Self::BaseField = <ArkG1Config as sw::SWCurveConfig>::COEFF_B;
216            const GENERATOR: sw::Affine<Self> = sw::Affine::new_unchecked(
217                <ArkG1Config as sw::SWCurveConfig>::GENERATOR.x,
218                <ArkG1Config as sw::SWCurveConfig>::GENERATOR.y,
219            );
220        }
221
222        impl CompatibleConfig<ArkG1Config> for ExtConfig {}
223
224        #[test]
225        fn sw_affine_roundtrip() {
226            let point: sw::Affine<ArkG1Config> = rand_point();
227            let ext: sw::Affine<ExtConfig> = point.transmute_into();
228            let back: &sw::Affine<ArkG1Config> = ext.transmute_ref();
229            assert_eq!(*back, point);
230        }
231
232        #[test]
233        fn sw_projective_roundtrip() {
234            let point: sw::Projective<ArkG1Config> = rand_point();
235            let ext: sw::Projective<ExtConfig> = point.transmute_into();
236            let back: &sw::Projective<ArkG1Config> = ext.transmute_ref();
237            assert_eq!(*back, point);
238        }
239
240        #[test]
241        fn sw_affine_slice_roundtrip() {
242            let ark_points: Vec<sw::Affine<ArkG1Config>> = rand_points(5);
243            let ext_points: Vec<sw::Affine<ExtConfig>> = ark_points
244                .iter()
245                .copied()
246                .map(|p| p.transmute_into())
247                .collect();
248            let back: &[sw::Affine<ArkG1Config>] = ext_points.as_slice().transmute_ref();
249            assert_eq!(back, ark_points.as_slice());
250        }
251    }
252
253    mod te_test {
254        use super::*;
255        use ark_ed25519::EdwardsConfig as ArkTeConfig;
256
257        #[derive(Clone, Copy)]
258        struct ExtConfig;
259
260        impl CurveConfig for ExtConfig {
261            type BaseField = <ArkTeConfig as CurveConfig>::BaseField;
262            type ScalarField = <ArkTeConfig as CurveConfig>::ScalarField;
263            const COFACTOR: &'static [u64] = <ArkTeConfig as CurveConfig>::COFACTOR;
264            const COFACTOR_INV: Self::ScalarField = <ArkTeConfig as CurveConfig>::COFACTOR_INV;
265        }
266
267        impl te::TECurveConfig for ExtConfig {
268            const COEFF_A: Self::BaseField = <ArkTeConfig as te::TECurveConfig>::COEFF_A;
269            const COEFF_D: Self::BaseField = <ArkTeConfig as te::TECurveConfig>::COEFF_D;
270            const GENERATOR: te::Affine<Self> = te::Affine::new_unchecked(
271                <ArkTeConfig as te::TECurveConfig>::GENERATOR.x,
272                <ArkTeConfig as te::TECurveConfig>::GENERATOR.y,
273            );
274            type MontCurveConfig = ArkTeConfig;
275        }
276
277        impl CompatibleConfig<ArkTeConfig> for ExtConfig {}
278
279        #[test]
280        fn te_affine_roundtrip() {
281            let point: te::Affine<ArkTeConfig> = rand_point();
282            let ext: te::Affine<ExtConfig> = point.transmute_into();
283            let back: &te::Affine<ArkTeConfig> = ext.transmute_ref();
284            assert_eq!(*back, point);
285        }
286
287        #[test]
288        fn te_projective_roundtrip() {
289            let point: te::Projective<ArkTeConfig> = rand_point();
290            let ext: te::Projective<ExtConfig> = point.transmute_into();
291            let back: &te::Projective<ArkTeConfig> = ext.transmute_ref();
292            assert_eq!(*back, point);
293        }
294
295        #[test]
296        fn te_affine_slice_roundtrip() {
297            let ark_points: Vec<te::Affine<ArkTeConfig>> = rand_points(5);
298            let ext_points: Vec<te::Affine<ExtConfig>> = ark_points
299                .iter()
300                .copied()
301                .map(|p| p.transmute_into())
302                .collect();
303            let back: &[te::Affine<ArkTeConfig>] = ext_points.as_slice().transmute_ref();
304            assert_eq!(back, ark_points.as_slice());
305        }
306    }
307}