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