1use crate::utils::{
39 self, invalid_projective_fallback, Error, HostcallResult, IntoAffineSafe, FAIL_MSG,
40};
41use alloc::vec::Vec;
42use ark_ec::{AffineRepr, CurveConfig};
43use ark_ed_on_bls12_381_bandersnatch_ext::CurveHooks;
44use sp_runtime_interface::{
45 pass_by::{PassFatPointerAndRead, PassFatPointerAndWrite},
46 runtime_interface,
47};
48
49pub type BandersnatchConfig = ark_ed_on_bls12_381_bandersnatch_ext::BandersnatchConfig<HostHooks>;
51
52pub type EdwardsConfig = ark_ed_on_bls12_381_bandersnatch_ext::EdwardsConfig<HostHooks>;
54pub type EdwardsAffine = ark_ed_on_bls12_381_bandersnatch_ext::EdwardsAffine<HostHooks>;
56pub type EdwardsProjective = ark_ed_on_bls12_381_bandersnatch_ext::EdwardsProjective<HostHooks>;
58
59pub type SWConfig = ark_ed_on_bls12_381_bandersnatch_ext::SWConfig<HostHooks>;
61pub type SWAffine = ark_ed_on_bls12_381_bandersnatch_ext::SWAffine<HostHooks>;
63pub type SWProjective = ark_ed_on_bls12_381_bandersnatch_ext::SWProjective<HostHooks>;
65
66pub type ScalarField = <BandersnatchConfig as CurveConfig>::ScalarField;
68
69#[derive(Copy, Clone)]
71pub struct HostHooks;
72
73impl CurveHooks for HostHooks {
74 fn msm_te(bases: &[EdwardsAffine], scalars: &[ScalarField]) -> EdwardsProjective {
75 let mut out = utils::buffer_for::<EdwardsAffine>();
76 match host_calls::ed_on_bls12_381_bandersnatch_msm(
77 &utils::encode(bases),
78 &utils::encode(scalars),
79 &mut out,
80 ) {
81 Ok(()) => utils::decode::<EdwardsAffine>(&out).expect(FAIL_MSG).into_group(),
82 Err(Error::DegeneratePoint) => invalid_projective_fallback::<EdwardsConfig>(),
83 Err(_) => panic!("{FAIL_MSG}"),
84 }
85 }
86
87 fn mul_projective_te(base: &EdwardsProjective, scalar: &[u64]) -> EdwardsProjective {
88 let Some(base_aff) = base.into_affine_safe() else {
94 return invalid_projective_fallback::<EdwardsConfig>();
95 };
96 let mut out = utils::buffer_for::<EdwardsAffine>();
97 match host_calls::ed_on_bls12_381_bandersnatch_mul(
98 &utils::encode(base_aff),
99 &utils::encode(scalar),
100 &mut out,
101 ) {
102 Ok(()) => utils::decode::<EdwardsAffine>(&out).expect(FAIL_MSG).into_group(),
103 Err(Error::DegeneratePoint) => invalid_projective_fallback::<EdwardsConfig>(),
104 Err(_) => panic!("{FAIL_MSG}"),
105 }
106 }
107}
108
109#[runtime_interface]
123pub trait HostCalls {
124 fn ed_on_bls12_381_bandersnatch_msm(
131 bases: PassFatPointerAndRead<&[u8]>,
132 scalars: PassFatPointerAndRead<&[u8]>,
133 out: PassFatPointerAndWrite<&mut [u8]>,
134 ) -> HostcallResult {
135 utils::msm_te::<ark_ed_on_bls12_381_bandersnatch::EdwardsConfig>(bases, scalars, out)
136 }
137
138 fn ed_on_bls12_381_bandersnatch_mul(
145 base: PassFatPointerAndRead<&[u8]>,
146 scalar: PassFatPointerAndRead<&[u8]>,
147 out: PassFatPointerAndWrite<&mut [u8]>,
148 ) -> HostcallResult {
149 utils::mul_te::<ark_ed_on_bls12_381_bandersnatch::EdwardsConfig>(base, scalar, out)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::utils::testing::*;
157 use ark_ec::{
158 twisted_edwards::{Affine as TEAffine, Projective as TEProjective, TECurveConfig},
159 CurveGroup,
160 };
161 use ark_ed_on_bls12_381_bandersnatch::{EdwardsConfig as RawConfig, Fq, Fr};
162 use ark_ff::{AdditiveGroup, MontFp, PrimeField, Zero};
163
164 #[test]
165 fn mul_works() {
166 mul_te_test::<EdwardsAffine, ark_ed_on_bls12_381_bandersnatch::EdwardsAffine>();
167 }
168
169 #[test]
170 fn msm_works() {
171 msm_te_test::<EdwardsAffine, ark_ed_on_bls12_381_bandersnatch::EdwardsAffine>();
172 }
173
174 #[test]
175 fn mul_works_sw() {
176 mul_test::<SWAffine, ark_ed_on_bls12_381_bandersnatch::SWAffine>();
177 }
178
179 #[test]
180 fn msm_works_sw() {
181 msm_test::<SWAffine, ark_ed_on_bls12_381_bandersnatch::SWAffine>();
182 }
183
184 fn y2_non_subgroup<P: TECurveConfig<BaseField = Fq>>() -> TEAffine<P> {
189 TEAffine::<P>::get_point_from_y_unchecked(Fq::from(2u64), false)
190 .expect("y=2 must yield a valid TEAffine point")
191 }
192
193 #[test]
194 fn host_mul_with_z_zero_result_returns_fallback() {
195 let proj: TEProjective<RawConfig> = y2_non_subgroup::<RawConfig>().into_group();
197 let raw_res = <RawConfig as TECurveConfig>::mul_projective(&proj, Fr::MODULUS.0.as_ref());
198 assert!(raw_res.z.is_zero(), "test precondition: y=2 * Fr::MODULUS must hit z=0");
199
200 let scalar_bigint: Vec<u64> = Fr::MODULUS.0.to_vec();
203 let input_enc = utils::encode(y2_non_subgroup::<EdwardsConfig>());
204 let scalar_enc = utils::encode(scalar_bigint);
205 let mut out = utils::buffer_for::<EdwardsAffine>();
206 let err = host_calls::ed_on_bls12_381_bandersnatch_mul(&input_enc, &scalar_enc, &mut out)
207 .expect_err("z=0 result must surface as Err(DegeneratePoint)");
208 assert_eq!(err, Error::DegeneratePoint);
209
210 let p_ext: EdwardsProjective = y2_non_subgroup::<EdwardsConfig>().into_group();
213 let r = <HostHooks as CurveHooks>::mul_projective_te(&p_ext, Fr::MODULUS.0.as_ref());
214 assert_eq!(
215 r,
216 invalid_projective_fallback::<EdwardsConfig>(),
217 "hook must return all-zero projective on degenerate"
218 );
219 }
220
221 #[test]
222 fn mul_projective_with_z_zero_input_returns_fallback() {
223 use ark_std::{test_rng, UniformRand};
224 let mut rng = test_rng();
225 let y = Fq::rand(&mut rng);
226 let t = Fq::rand(&mut rng);
227 let p = EdwardsProjective::new_unchecked(Fq::ZERO, y, t, Fq::ZERO);
228 let r = <HostHooks as CurveHooks>::mul_projective_te(&p, &[7u64, 0, 0, 0]);
229 assert_eq!(
230 r,
231 invalid_projective_fallback::<EdwardsConfig>(),
232 "z=0 input must yield all-zero coordinate projective"
233 );
234 }
235
236 #[test]
237 fn fallback_is_invalid_projective_point() {
238 let fallback = invalid_projective_fallback::<EdwardsConfig>();
240 assert!(fallback.x.is_zero(), "fallback x must be zero");
241 assert!(fallback.y.is_zero(), "fallback y must be zero");
242 assert!(fallback.t.is_zero(), "fallback t must be zero");
243 assert!(fallback.z.is_zero(), "fallback z must be zero");
244
245 assert!(!fallback.is_zero(), "all-zero projective must NOT be considered identity");
252 assert!(
253 fallback.into_affine_safe().is_none(),
254 "all-zero projective must map to None via IntoAffineSafe",
255 );
256
257 let degenerate = TEProjective::<RawConfig>::new_unchecked(
266 Fq::ZERO, Fq::from(7u64), Fq::from(11u64), Fq::ZERO, );
271 assert!(
272 degenerate.into_affine_safe().is_none(),
273 "z=0 projective must map to None via IntoAffineSafe",
274 );
275 }
276
277 fn exceptional_pair() -> (EdwardsAffine, EdwardsAffine, EdwardsAffine) {
281 let xa: Fq = MontFp!(
282 "12611587488970178020234800979835231446181428428390492190317266241455236381927"
283 );
284 let ya: Fq =
285 MontFp!("8625363597705895091270672088731506059935752500467284843225771956507605756711");
286 let xb: Fq =
287 MontFp!("5253339395048946693631279295832797565125937378490576959411837397991361739535");
288 let yb: Fq = MontFp!(
289 "24752777243643877000069062635360441442644758493268974317933177186378585499408"
290 );
291 let x_sum: Fq = MontFp!(
293 "30239213723729448420307207485613680945165091785466061697591732383921178212543"
294 );
295 let y_sum: Fq = MontFp!(
296 "48407687168732128978323921344344221491641898681064657528705691267288289221251"
297 );
298 (
299 EdwardsAffine::new_unchecked(xa, ya),
300 EdwardsAffine::new_unchecked(xb, yb),
301 EdwardsAffine::new_unchecked(x_sum, y_sum),
302 )
303 }
304
305 #[test]
306 fn hwcd_exceptional_pair_produces_all_zero_projective() {
307 let (a, b, _expected_sum) = exceptional_pair();
308 assert!(a.is_on_curve(), "point A must be on curve");
309 assert!(b.is_on_curve(), "point B must be on curve");
310
311 let a_proj: EdwardsProjective = a.into_group();
313 let b_proj: EdwardsProjective = b.into_group();
314 let sum = a_proj + b_proj;
315 assert!(sum.x.is_zero(), "exceptional sum x must be zero");
316 assert!(sum.y.is_zero(), "exceptional sum y must be zero");
317 assert!(sum.t.is_zero(), "exceptional sum t must be zero");
318 assert!(sum.z.is_zero(), "exceptional sum z must be zero");
319 }
320
321 #[test]
322 fn hwcd_exceptional_pair_recovers_via_sage_sum() {
323 let (a, b, sage_sum) = exceptional_pair();
324 let a_proj: EdwardsProjective = a.into_group();
325 let b_proj: EdwardsProjective = b.into_group();
326 let sage_sum_proj: EdwardsProjective = sage_sum.into_group();
327
328 let ark_sum = a_proj + b_proj;
331 let a_plus_ark_sum = a_proj + ark_sum;
332 assert_eq!(
333 a_plus_ark_sum,
334 invalid_projective_fallback::<EdwardsConfig>(),
335 "A + (A+B from arkworks) must produce all-zero projective"
336 );
337
338 let two_a = a_proj + a_proj;
341 let two_a_plus_b = two_a + b_proj;
342 let a_plus_sage_sum = a_proj + sage_sum_proj;
343 assert_eq!(
344 a_plus_sage_sum.into_affine(),
345 two_a_plus_b.into_affine(),
346 "A + (A+B from Sage) must equal 2*A + B"
347 );
348 }
349
350 #[test]
351 fn hwcd_exceptional_pair_msm_produces_all_zero() {
352 use ark_ec::VariableBaseMSM;
353
354 let (a, b, _expected_sum) = exceptional_pair();
355
356 let bases = vec![a, b];
359 let scalars = vec![Fr::from(2u64), Fr::from(1u64)];
360 let result = EdwardsProjective::msm(&bases, &scalars).unwrap();
361
362 assert_eq!(
363 result,
364 invalid_projective_fallback::<EdwardsConfig>(),
365 "msm([A, B], [2, 1]) must produce invalid projective fallback"
366 );
367 }
368
369 #[test]
370 fn hwcd_exceptional_pair_msm_te_returns_invalid_projective() {
371 let (a, b, _expected_sum) = exceptional_pair();
372
373 let bases = vec![a, b];
376 let scalars = vec![Fr::from(2u64), Fr::from(1u64)];
377 let result = <HostHooks as CurveHooks>::msm_te(&bases, &scalars);
378
379 assert_eq!(
380 result,
381 invalid_projective_fallback::<EdwardsConfig>(),
382 "msm_te must return invalid projective fallback"
383 );
384 }
385
386 #[test]
387 fn y2_point_deserialize_checked_vs_unchecked() {
388 use ark_scale::ark_serialize::{
389 CanonicalDeserialize, CanonicalSerialize, Compress, Validate,
390 };
391
392 let p = y2_non_subgroup::<EdwardsConfig>();
393 assert!(p.is_on_curve(), "y=2 point must be on curve");
394 assert!(
395 !p.is_in_correct_subgroup_assuming_on_curve(),
396 "y=2 point must NOT be in the prime-order subgroup",
397 );
398
399 let mut bytes = Vec::new();
400 p.serialize_with_mode(&mut bytes, Compress::No).unwrap();
401
402 let decoded =
404 EdwardsAffine::deserialize_with_mode(&bytes[..], Compress::No, Validate::No).unwrap();
405 assert_eq!(decoded, p);
406
407 assert!(
409 EdwardsAffine::deserialize_with_mode(&bytes[..], Compress::No, Validate::Yes).is_err(),
410 "Validate::Yes must reject non-subgroup point",
411 );
412 }
413}