1#![allow(non_snake_case)]
17
18#[cfg(all(feature = "alloc", not(verify)))]
162use alloc::vec::Vec;
163
164use core::array::TryFromSliceError;
165use core::borrow::Borrow;
166use core::fmt::Debug;
167use core::iter::Sum;
168use core::ops::{Add, Neg, Sub};
169use core::ops::{AddAssign, SubAssign};
170use core::ops::{Mul, MulAssign};
171
172#[cfg(any(test, feature = "rand_core"))]
173use rand_core::CryptoRngCore;
174
175#[cfg(feature = "digest")]
176use digest::generic_array::typenum::U64;
177#[cfg(feature = "digest")]
178use digest::Digest;
179
180use crate::constants;
181use crate::field::FieldElement;
182
183#[cfg(feature = "group")]
184use {
185 group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding},
186 rand_core::RngCore,
187 subtle::CtOption,
188};
189
190use subtle::Choice;
191#[cfg(all(feature = "alloc", not(verify)))]
192use subtle::ConditionallyNegatable;
193use subtle::ConditionallySelectable;
194use subtle::ConstantTimeEq;
195
196#[cfg(feature = "zeroize")]
197use zeroize::Zeroize;
198
199#[cfg(feature = "precomputed-tables")]
200use crate::edwards::EdwardsBasepointTable;
201use crate::edwards::EdwardsPoint;
202
203use crate::scalar::Scalar;
204
205#[cfg(feature = "precomputed-tables")]
206use crate::traits::BasepointTable;
207use crate::traits::Identity;
208#[cfg(feature = "alloc")]
209use crate::traits::{MultiscalarMul, VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul};
210
211#[derive(Copy, Clone, Eq, PartialEq, Hash)]
220pub struct CompressedRistretto(pub [u8; 32]);
221
222impl ConstantTimeEq for CompressedRistretto {
223 fn ct_eq(&self, other: &CompressedRistretto) -> Choice {
224 self.as_bytes().ct_eq(other.as_bytes())
225 }
226}
227
228impl CompressedRistretto {
229 pub const fn to_bytes(&self) -> [u8; 32] {
231 self.0
232 }
233
234 pub const fn as_bytes(&self) -> &[u8; 32] {
236 &self.0
237 }
238
239 pub fn from_slice(bytes: &[u8]) -> Result<CompressedRistretto, TryFromSliceError> {
246 #[allow(clippy::redundant_closure)]
247 bytes.try_into().map(|b| CompressedRistretto(b))
248 }
249
250 pub fn decompress(&self) -> Option<RistrettoPoint> {
258 let (s_encoding_is_canonical, s_is_negative, s) = decompress::step_1(self);
259
260 if (!s_encoding_is_canonical | s_is_negative).into() {
261 return None;
262 }
263
264 let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s);
265
266 if (!ok | t_is_negative | y_is_zero).into() {
267 None
268 } else {
269 Some(res)
270 }
271 }
272}
273
274mod decompress {
275 use super::*;
276
277 pub(super) fn step_1(repr: &CompressedRistretto) -> (Choice, Choice, FieldElement) {
278 let s = FieldElement::from_bytes(repr.as_bytes());
290 let s_bytes_check = s.to_bytes();
291 let s_encoding_is_canonical = s_bytes_check[..].ct_eq(repr.as_bytes());
292 let s_is_negative = s.is_negative();
293
294 (s_encoding_is_canonical, s_is_negative, s)
295 }
296
297 pub(super) fn step_2(s: FieldElement) -> (Choice, Choice, Choice, RistrettoPoint) {
298 let one = FieldElement::ONE;
300 let ss = s.square();
301 let u1 = &one - &ss; let u2 = &one + &ss; let u2_sqr = u2.square(); let neg_d = -&constants::EDWARDS_D;
307 let u1_sq = u1.square();
308 let neg_d_u1_sq = &neg_d * &u1_sq;
309 let v = &neg_d_u1_sq - &u2_sqr;
310
311 let v_u2_sqr = &v * &u2_sqr;
312 let (ok, I) = v_u2_sqr.invsqrt(); let Dx = &I * &u2; let Dx_v = &Dx * &v;
316 let Dy = &I * &Dx_v; let s_plus_s = &s + &s;
320 let mut x = &s_plus_s * &Dx;
321 let x_neg = x.is_negative();
322 let x_negated = -&x;
323 x.conditional_assign(&x_negated, x_neg);
324
325 let y = &u1 * &Dy;
327
328 let t = &x * &y;
330
331 (
332 ok,
333 t.is_negative(),
334 y.is_zero(),
335 RistrettoPoint(EdwardsPoint {
336 X: x,
337 Y: y,
338 Z: one,
339 T: t,
340 }),
341 )
342 }
343}
344
345impl Identity for CompressedRistretto {
346 fn identity() -> CompressedRistretto {
347 CompressedRistretto([0u8; 32])
348 }
349}
350
351impl Default for CompressedRistretto {
352 fn default() -> CompressedRistretto {
353 CompressedRistretto::identity()
354 }
355}
356
357impl TryFrom<&[u8]> for CompressedRistretto {
358 type Error = TryFromSliceError;
359
360 fn try_from(slice: &[u8]) -> Result<CompressedRistretto, TryFromSliceError> {
361 Self::from_slice(slice)
362 }
363}
364
365#[cfg(feature = "serde")]
374use serde::de::Visitor;
375#[cfg(feature = "serde")]
376use serde::{Deserialize, Deserializer, Serialize, Serializer};
377
378#[cfg(feature = "serde")]
379impl Serialize for RistrettoPoint {
380 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
381 where
382 S: Serializer,
383 {
384 use serde::ser::SerializeTuple;
385 let mut tup = serializer.serialize_tuple(32)?;
386 for byte in self.compress().as_bytes().iter() {
387 tup.serialize_element(byte)?;
388 }
389 tup.end()
390 }
391}
392
393#[cfg(feature = "serde")]
394impl Serialize for CompressedRistretto {
395 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
396 where
397 S: Serializer,
398 {
399 use serde::ser::SerializeTuple;
400 let mut tup = serializer.serialize_tuple(32)?;
401 for byte in self.as_bytes().iter() {
402 tup.serialize_element(byte)?;
403 }
404 tup.end()
405 }
406}
407
408#[cfg(feature = "serde")]
409impl<'de> Deserialize<'de> for RistrettoPoint {
410 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
411 where
412 D: Deserializer<'de>,
413 {
414 struct RistrettoPointVisitor;
415
416 impl<'de> Visitor<'de> for RistrettoPointVisitor {
417 type Value = RistrettoPoint;
418
419 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
420 formatter.write_str("a valid point in Ristretto format")
421 }
422
423 fn visit_seq<A>(self, mut seq: A) -> Result<RistrettoPoint, A::Error>
424 where
425 A: serde::de::SeqAccess<'de>,
426 {
427 let mut bytes = [0u8; 32];
428 #[allow(clippy::needless_range_loop)]
429 for i in 0..32 {
430 bytes[i] = seq
431 .next_element()?
432 .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
433 }
434 CompressedRistretto(bytes)
435 .decompress()
436 .ok_or_else(|| serde::de::Error::custom("decompression failed"))
437 }
438 }
439
440 deserializer.deserialize_tuple(32, RistrettoPointVisitor)
441 }
442}
443
444#[cfg(feature = "serde")]
445impl<'de> Deserialize<'de> for CompressedRistretto {
446 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
447 where
448 D: Deserializer<'de>,
449 {
450 struct CompressedRistrettoVisitor;
451
452 impl<'de> Visitor<'de> for CompressedRistrettoVisitor {
453 type Value = CompressedRistretto;
454
455 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
456 formatter.write_str("32 bytes of data")
457 }
458
459 fn visit_seq<A>(self, mut seq: A) -> Result<CompressedRistretto, A::Error>
460 where
461 A: serde::de::SeqAccess<'de>,
462 {
463 let mut bytes = [0u8; 32];
464 #[allow(clippy::needless_range_loop)]
465 for i in 0..32 {
466 bytes[i] = seq
467 .next_element()?
468 .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
469 }
470 Ok(CompressedRistretto(bytes))
471 }
472 }
473
474 deserializer.deserialize_tuple(32, CompressedRistrettoVisitor)
475 }
476}
477
478#[derive(Copy, Clone)]
494pub struct RistrettoPoint(pub(crate) EdwardsPoint);
495
496impl RistrettoPoint {
497 pub fn compress(&self) -> CompressedRistretto {
499 let mut X = self.0.X;
500 let mut Y = self.0.Y;
501 let Z = &self.0.Z;
502 let T = &self.0.T;
503
504 let z_plus_y = Z + &Y;
505 let z_minus_y = Z - &Y;
506 let u1 = &z_plus_y * &z_minus_y;
507 let u2 = &X * &Y;
508 let u2_sq = u2.square();
510 let u1_u2_sq = &u1 * &u2_sq;
511 let (_, invsqrt) = u1_u2_sq.invsqrt();
512 let i1 = &invsqrt * &u1;
513 let i2 = &invsqrt * &u2;
514 let i2_T = &i2 * T;
515 let z_inv = &i1 * &i2_T;
516 let mut den_inv = i2;
517
518 let iX = &X * &constants::SQRT_M1;
519 let iY = &Y * &constants::SQRT_M1;
520 let ristretto_magic = &constants::INVSQRT_A_MINUS_D;
521 let enchanted_denominator = &i1 * ristretto_magic;
522
523 let t_z_inv = T * &z_inv;
524 let rotate = t_z_inv.is_negative();
525
526 X.conditional_assign(&iY, rotate);
527 Y.conditional_assign(&iX, rotate);
528 den_inv.conditional_assign(&enchanted_denominator, rotate);
529
530 let x_z_inv = &X * &z_inv;
531 let y_sign = x_z_inv.is_negative();
532 let y_neg = -&Y;
533 Y.conditional_assign(&y_neg, y_sign);
534
535 let z_minus_y2 = Z - &Y;
536 let mut s = &den_inv * &z_minus_y2;
537 let s_is_negative = s.is_negative();
538 let s_neg = -&s;
539 s.conditional_assign(&s_neg, s_is_negative);
540
541 CompressedRistretto(s.to_bytes())
542 }
543
544 #[cfg_attr(feature = "rand_core", doc = "```")]
553 #[cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
554 #[cfg(all(feature = "alloc", not(verify)))]
573 pub fn double_and_compress_batch<'a, I>(points: I) -> Vec<CompressedRistretto>
574 where
575 I: IntoIterator<Item = &'a RistrettoPoint>,
576 {
577 #[derive(Copy, Clone, Debug)]
578 struct BatchCompressState {
579 e: FieldElement,
580 f: FieldElement,
581 g: FieldElement,
582 h: FieldElement,
583 eg: FieldElement,
584 fh: FieldElement,
585 }
586
587 impl BatchCompressState {
588 fn efgh(&self) -> FieldElement {
589 &self.eg * &self.fh
590 }
591 }
592
593 impl<'a> From<&'a RistrettoPoint> for BatchCompressState {
594 #[rustfmt::skip] fn from(P: &'a RistrettoPoint) -> BatchCompressState {
596 let XX = P.0.X.square();
597 let YY = P.0.Y.square();
598 let ZZ = P.0.Z.square();
599 let dTT = &P.0.T.square() * &constants::EDWARDS_D;
600
601 let e = &P.0.X * &(&P.0.Y + &P.0.Y); let f = &ZZ + &dTT; let g = &YY + &XX; let h = &ZZ - &dTT; let eg = &e * &g;
607 let fh = &f * &h;
608
609 BatchCompressState{ e, f, g, h, eg, fh }
610 }
611 }
612
613 let states: Vec<BatchCompressState> =
614 points.into_iter().map(BatchCompressState::from).collect();
615
616 let mut invs: Vec<FieldElement> = states.iter().map(|state| state.efgh()).collect();
617
618 FieldElement::batch_invert(&mut invs[..]);
619
620 states
621 .iter()
622 .zip(invs.iter())
623 .map(|(state, inv): (&BatchCompressState, &FieldElement)| {
624 let Zinv = &state.eg * inv;
625 let Tinv = &state.fh * inv;
626
627 let mut magic = constants::INVSQRT_A_MINUS_D;
628
629 let negcheck1 = (&state.eg * &Zinv).is_negative();
630
631 let mut e = state.e;
632 let mut g = state.g;
633 let mut h = state.h;
634
635 let minus_e = -&e;
636 let f_times_sqrta = &state.f * &constants::SQRT_M1;
637
638 e.conditional_assign(&state.g, negcheck1);
639 g.conditional_assign(&minus_e, negcheck1);
640 h.conditional_assign(&f_times_sqrta, negcheck1);
641
642 magic.conditional_assign(&constants::SQRT_M1, negcheck1);
643
644 let negcheck2 = (&(&h * &e) * &Zinv).is_negative();
645
646 g.conditional_negate(negcheck2);
647
648 let mut s = &(&h - &g) * &(&magic * &(&g * &Tinv));
649
650 let s_is_negative = s.is_negative();
651 s.conditional_negate(s_is_negative);
652
653 CompressedRistretto(s.to_bytes())
654 })
655 .collect()
656 }
657
658 fn coset4(&self) -> [EdwardsPoint; 4] {
660 [
661 self.0,
662 self.0 + constants::EIGHT_TORSION[2],
663 self.0 + constants::EIGHT_TORSION[4],
664 self.0 + constants::EIGHT_TORSION[6],
665 ]
666 }
667
668 pub(crate) fn elligator_ristretto_flavor(r_0: &FieldElement) -> RistrettoPoint {
677 let i = &constants::SQRT_M1;
678 let d = &constants::EDWARDS_D;
679 let one_minus_d_sq = &constants::ONE_MINUS_EDWARDS_D_SQUARED;
680 let d_minus_one_sq = &constants::EDWARDS_D_MINUS_ONE_SQUARED;
681 let mut c = constants::MINUS_ONE;
682
683 let one = FieldElement::ONE;
684
685 let r_0_sq = r_0.square();
686 let r = i * &r_0_sq;
687 let r_plus_one = &r + &one;
688 let N_s = &r_plus_one * one_minus_d_sq;
689 let d_times_r = d * &r;
690 let c_minus_dr = &c - &d_times_r;
691 let r_plus_d = &r + d;
692 let D = &c_minus_dr * &r_plus_d;
693
694 let (Ns_D_is_sq, mut s) = FieldElement::sqrt_ratio_i(&N_s, &D);
695 let mut s_prime = &s * r_0;
696 let s_prime_is_pos = !s_prime.is_negative();
697 let s_prime_neg = -&s_prime;
698 s_prime.conditional_assign(&s_prime_neg, s_prime_is_pos);
699
700 let not_sq = !Ns_D_is_sq;
701 s.conditional_assign(&s_prime, not_sq);
702 c.conditional_assign(&r, not_sq);
703
704 let r_minus_one = &r - &one;
705 let c_r_minus_one = &c * &r_minus_one;
706 let c_r_minus_one_d = &c_r_minus_one * d_minus_one_sq;
707 let N_t = &c_r_minus_one_d - &D;
708 let s_sq = s.square();
709
710 use crate::backend::serial::curve_models::CompletedPoint;
711
712 let s_plus_s = &s + &s;
713 let cp_X = &s_plus_s * &D;
714 let cp_Z = &N_t * &constants::SQRT_AD_MINUS_ONE;
715 let cp_Y = &FieldElement::ONE - &s_sq;
716 let cp_T = &FieldElement::ONE + &s_sq;
717
718 RistrettoPoint(
720 CompletedPoint {
721 X: cp_X,
722 Z: cp_Z,
723 Y: cp_Y,
724 T: cp_T,
725 }
726 .as_extended(),
727 )
728 }
729
730 #[cfg(any(test, feature = "rand_core"))]
731 pub fn random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
749 let mut uniform_bytes = [0u8; 64];
750 rng.fill_bytes(&mut uniform_bytes);
751
752 RistrettoPoint::from_uniform_bytes(&uniform_bytes)
753 }
754
755 #[cfg(feature = "digest")]
756 #[cfg_attr(feature = "digest", doc = "```")]
773 #[cfg_attr(not(feature = "digest"), doc = "```ignore")]
774 pub fn hash_from_bytes<D>(input: &[u8]) -> RistrettoPoint
786 where
787 D: Digest<OutputSize = U64> + Default,
788 {
789 let mut hash = D::default();
790 hash.update(input);
791 RistrettoPoint::from_hash(hash)
792 }
793
794 #[cfg(feature = "digest")]
795 pub fn from_hash<D>(hash: D) -> RistrettoPoint
801 where
802 D: Digest<OutputSize = U64> + Default,
803 {
804 let output = hash.finalize();
806 let mut output_bytes = [0u8; 64];
807 output_bytes.copy_from_slice(output.as_slice());
808
809 RistrettoPoint::from_uniform_bytes(&output_bytes)
810 }
811
812 pub fn from_uniform_bytes(bytes: &[u8; 64]) -> RistrettoPoint {
824 let mut r_1_bytes = [0u8; 32];
827 r_1_bytes.copy_from_slice(&bytes[0..32]);
828 let r_1 = FieldElement::from_bytes(&r_1_bytes);
829 let R_1 = RistrettoPoint::elligator_ristretto_flavor(&r_1);
830
831 let mut r_2_bytes = [0u8; 32];
832 r_2_bytes.copy_from_slice(&bytes[32..64]);
833 let r_2 = FieldElement::from_bytes(&r_2_bytes);
834 let R_2 = RistrettoPoint::elligator_ristretto_flavor(&r_2);
835
836 R_1 + R_2
839 }
840}
841
842impl Identity for RistrettoPoint {
843 fn identity() -> RistrettoPoint {
844 RistrettoPoint(EdwardsPoint::identity())
845 }
846}
847
848impl Default for RistrettoPoint {
849 fn default() -> RistrettoPoint {
850 RistrettoPoint::identity()
851 }
852}
853
854impl PartialEq for RistrettoPoint {
859 fn eq(&self, other: &RistrettoPoint) -> bool {
860 self.ct_eq(other).into()
861 }
862}
863
864impl ConstantTimeEq for RistrettoPoint {
865 fn ct_eq(&self, other: &RistrettoPoint) -> Choice {
872 let X1Y2 = &self.0.X * &other.0.Y;
873 let Y1X2 = &self.0.Y * &other.0.X;
874 let X1X2 = &self.0.X * &other.0.X;
875 let Y1Y2 = &self.0.Y * &other.0.Y;
876
877 X1Y2.ct_eq(&Y1X2) | X1X2.ct_eq(&Y1Y2)
878 }
879}
880
881impl Eq for RistrettoPoint {}
882
883impl<'a> Add<&'a RistrettoPoint> for &RistrettoPoint {
888 type Output = RistrettoPoint;
889
890 fn add(self, other: &'a RistrettoPoint) -> RistrettoPoint {
891 RistrettoPoint(self.0 + other.0)
892 }
893}
894
895define_add_variants!(
896 LHS = RistrettoPoint,
897 RHS = RistrettoPoint,
898 Output = RistrettoPoint
899);
900
901impl AddAssign<&RistrettoPoint> for RistrettoPoint {
902 fn add_assign(&mut self, _rhs: &RistrettoPoint) {
903 *self = (self as &RistrettoPoint) + _rhs;
904 }
905}
906
907define_add_assign_variants!(LHS = RistrettoPoint, RHS = RistrettoPoint);
908
909impl<'a> Sub<&'a RistrettoPoint> for &RistrettoPoint {
910 type Output = RistrettoPoint;
911
912 fn sub(self, other: &'a RistrettoPoint) -> RistrettoPoint {
913 RistrettoPoint(self.0 - other.0)
914 }
915}
916
917define_sub_variants!(
918 LHS = RistrettoPoint,
919 RHS = RistrettoPoint,
920 Output = RistrettoPoint
921);
922
923impl SubAssign<&RistrettoPoint> for RistrettoPoint {
924 fn sub_assign(&mut self, _rhs: &RistrettoPoint) {
925 *self = (self as &RistrettoPoint) - _rhs;
926 }
927}
928
929define_sub_assign_variants!(LHS = RistrettoPoint, RHS = RistrettoPoint);
930
931impl<T> Sum<T> for RistrettoPoint
932where
933 T: Borrow<RistrettoPoint>,
934{
935 fn sum<I>(iter: I) -> Self
936 where
937 I: Iterator<Item = T>,
938 {
939 iter.fold(RistrettoPoint::identity(), |acc, item| acc + item.borrow())
940 }
941}
942
943impl Neg for &RistrettoPoint {
944 type Output = RistrettoPoint;
945
946 fn neg(self) -> RistrettoPoint {
947 RistrettoPoint(-&self.0)
948 }
949}
950
951impl Neg for RistrettoPoint {
952 type Output = RistrettoPoint;
953
954 fn neg(self) -> RistrettoPoint {
955 -&self
956 }
957}
958
959impl<'a> MulAssign<&'a Scalar> for RistrettoPoint {
960 fn mul_assign(&mut self, scalar: &'a Scalar) {
961 let result = (self as &RistrettoPoint) * scalar;
962 *self = result;
963 }
964}
965
966impl<'a> Mul<&'a Scalar> for &RistrettoPoint {
967 type Output = RistrettoPoint;
968 fn mul(self, scalar: &'a Scalar) -> RistrettoPoint {
970 RistrettoPoint(self.0 * scalar)
971 }
972}
973
974impl<'a> Mul<&'a RistrettoPoint> for &Scalar {
975 type Output = RistrettoPoint;
976
977 fn mul(self, point: &'a RistrettoPoint) -> RistrettoPoint {
979 RistrettoPoint(self * point.0)
980 }
981}
982
983impl RistrettoPoint {
984 pub fn mul_base(scalar: &Scalar) -> Self {
989 #[cfg(not(feature = "precomputed-tables"))]
990 {
991 scalar * constants::RISTRETTO_BASEPOINT_POINT
992 }
993
994 #[cfg(feature = "precomputed-tables")]
995 {
996 scalar * constants::RISTRETTO_BASEPOINT_TABLE
997 }
998 }
999}
1000
1001define_mul_assign_variants!(LHS = RistrettoPoint, RHS = Scalar);
1002
1003define_mul_variants!(LHS = RistrettoPoint, RHS = Scalar, Output = RistrettoPoint);
1004define_mul_variants!(LHS = Scalar, RHS = RistrettoPoint, Output = RistrettoPoint);
1005
1006#[cfg(feature = "alloc")]
1014impl MultiscalarMul for RistrettoPoint {
1015 type Point = RistrettoPoint;
1016
1017 fn multiscalar_mul<I, J>(scalars: I, points: J) -> RistrettoPoint
1018 where
1019 I: IntoIterator,
1020 I::Item: Borrow<Scalar>,
1021 J: IntoIterator,
1022 J::Item: Borrow<RistrettoPoint>,
1023 {
1024 let extended_points = points.into_iter().map(|P| P.borrow().0);
1025 RistrettoPoint(EdwardsPoint::multiscalar_mul(scalars, extended_points))
1026 }
1027}
1028
1029#[cfg(feature = "alloc")]
1030impl VartimeMultiscalarMul for RistrettoPoint {
1031 type Point = RistrettoPoint;
1032
1033 fn optional_multiscalar_mul<I, J>(scalars: I, points: J) -> Option<RistrettoPoint>
1034 where
1035 I: IntoIterator,
1036 I::Item: Borrow<Scalar>,
1037 J: IntoIterator<Item = Option<RistrettoPoint>>,
1038 {
1039 let extended_points = points.into_iter().map(|opt_P| opt_P.map(|P| P.0));
1040
1041 EdwardsPoint::optional_multiscalar_mul(scalars, extended_points).map(RistrettoPoint)
1042 }
1043}
1044
1045#[cfg(feature = "alloc")]
1053pub struct VartimeRistrettoPrecomputation(crate::backend::VartimePrecomputedStraus);
1054
1055#[cfg(feature = "alloc")]
1056impl VartimePrecomputedMultiscalarMul for VartimeRistrettoPrecomputation {
1057 type Point = RistrettoPoint;
1058
1059 fn new<I>(static_points: I) -> Self
1060 where
1061 I: IntoIterator,
1062 I::Item: Borrow<Self::Point>,
1063 {
1064 Self(crate::backend::VartimePrecomputedStraus::new(
1065 static_points.into_iter().map(|P| P.borrow().0),
1066 ))
1067 }
1068
1069 fn len(&self) -> usize {
1070 self.0.len()
1071 }
1072
1073 fn is_empty(&self) -> bool {
1074 self.0.is_empty()
1075 }
1076
1077 fn optional_mixed_multiscalar_mul<I, J, K>(
1078 &self,
1079 static_scalars: I,
1080 dynamic_scalars: J,
1081 dynamic_points: K,
1082 ) -> Option<Self::Point>
1083 where
1084 I: IntoIterator,
1085 I::Item: Borrow<Scalar>,
1086 J: IntoIterator,
1087 J::Item: Borrow<Scalar>,
1088 K: IntoIterator<Item = Option<Self::Point>>,
1089 {
1090 self.0
1091 .optional_mixed_multiscalar_mul(
1092 static_scalars,
1093 dynamic_scalars,
1094 dynamic_points.into_iter().map(|P_opt| P_opt.map(|P| P.0)),
1095 )
1096 .map(RistrettoPoint)
1097 }
1098}
1099
1100#[cfg(not(verify))]
1101impl RistrettoPoint {
1102 pub fn vartime_double_scalar_mul_basepoint(
1105 a: &Scalar,
1106 A: &RistrettoPoint,
1107 b: &Scalar,
1108 ) -> RistrettoPoint {
1109 RistrettoPoint(EdwardsPoint::vartime_double_scalar_mul_basepoint(
1110 a, &A.0, b,
1111 ))
1112 }
1113}
1114
1115#[cfg(feature = "precomputed-tables")]
1128#[derive(Clone)]
1129#[repr(transparent)]
1130pub struct RistrettoBasepointTable(pub(crate) EdwardsBasepointTable);
1131
1132#[cfg(feature = "precomputed-tables")]
1133impl<'b> Mul<&'b Scalar> for &RistrettoBasepointTable {
1134 type Output = RistrettoPoint;
1135
1136 fn mul(self, scalar: &'b Scalar) -> RistrettoPoint {
1137 RistrettoPoint(&self.0 * scalar)
1138 }
1139}
1140
1141#[cfg(feature = "precomputed-tables")]
1142impl<'a> Mul<&'a RistrettoBasepointTable> for &Scalar {
1143 type Output = RistrettoPoint;
1144
1145 fn mul(self, basepoint_table: &'a RistrettoBasepointTable) -> RistrettoPoint {
1146 RistrettoPoint(self * &basepoint_table.0)
1147 }
1148}
1149
1150#[cfg(feature = "precomputed-tables")]
1151impl RistrettoBasepointTable {
1152 pub fn create(basepoint: &RistrettoPoint) -> RistrettoBasepointTable {
1154 RistrettoBasepointTable(EdwardsBasepointTable::create(&basepoint.0))
1155 }
1156
1157 pub fn basepoint(&self) -> RistrettoPoint {
1159 RistrettoPoint(self.0.basepoint())
1160 }
1161}
1162
1163impl ConditionallySelectable for RistrettoPoint {
1168 fn conditional_select(
1193 a: &RistrettoPoint,
1194 b: &RistrettoPoint,
1195 choice: Choice,
1196 ) -> RistrettoPoint {
1197 RistrettoPoint(EdwardsPoint::conditional_select(&a.0, &b.0, choice))
1198 }
1199}
1200
1201impl Debug for CompressedRistretto {
1206 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1207 write!(f, "CompressedRistretto: {:?}", self.as_bytes())
1208 }
1209}
1210
1211impl Debug for RistrettoPoint {
1212 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1213 let coset = self.coset4();
1214 write!(
1215 f,
1216 "RistrettoPoint: coset \n{:?}\n{:?}\n{:?}\n{:?}",
1217 coset[0], coset[1], coset[2], coset[3]
1218 )
1219 }
1220}
1221
1222#[cfg(feature = "group")]
1229impl group::Group for RistrettoPoint {
1230 type Scalar = Scalar;
1231
1232 fn random(mut rng: impl RngCore) -> Self {
1233 let mut uniform_bytes = [0u8; 64];
1235 rng.fill_bytes(&mut uniform_bytes);
1236 RistrettoPoint::from_uniform_bytes(&uniform_bytes)
1237 }
1238
1239 fn identity() -> Self {
1240 Identity::identity()
1241 }
1242
1243 fn generator() -> Self {
1244 constants::RISTRETTO_BASEPOINT_POINT
1245 }
1246
1247 fn is_identity(&self) -> Choice {
1248 self.ct_eq(&Identity::identity())
1249 }
1250
1251 fn double(&self) -> Self {
1252 self + self
1253 }
1254}
1255
1256#[cfg(feature = "group")]
1257impl GroupEncoding for RistrettoPoint {
1258 type Repr = [u8; 32];
1259
1260 fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
1261 let (s_encoding_is_canonical, s_is_negative, s) =
1262 decompress::step_1(&CompressedRistretto(*bytes));
1263
1264 let s_is_valid = s_encoding_is_canonical & !s_is_negative;
1265
1266 let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s);
1267
1268 CtOption::new(res, s_is_valid & ok & !t_is_negative & !y_is_zero)
1269 }
1270
1271 fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
1272 Self::from_bytes(bytes)
1274 }
1275
1276 fn to_bytes(&self) -> Self::Repr {
1277 self.compress().to_bytes()
1278 }
1279}
1280
1281#[cfg(feature = "group")]
1282impl PrimeGroup for RistrettoPoint {}
1283
1284#[cfg(feature = "group")]
1286impl CofactorGroup for RistrettoPoint {
1287 type Subgroup = Self;
1288
1289 fn clear_cofactor(&self) -> Self::Subgroup {
1290 *self
1291 }
1292
1293 fn into_subgroup(self) -> CtOption<Self::Subgroup> {
1294 CtOption::new(self, Choice::from(1))
1295 }
1296
1297 fn is_torsion_free(&self) -> Choice {
1298 Choice::from(1)
1299 }
1300}
1301
1302#[cfg(feature = "zeroize")]
1307impl Zeroize for CompressedRistretto {
1308 fn zeroize(&mut self) {
1309 self.0.zeroize();
1310 }
1311}
1312
1313#[cfg(feature = "zeroize")]
1314impl Zeroize for RistrettoPoint {
1315 fn zeroize(&mut self) {
1316 self.0.zeroize();
1317 }
1318}
1319
1320#[cfg(test)]
1325mod test {
1326 use super::*;
1327 use crate::edwards::CompressedEdwardsY;
1328
1329 use rand_core::OsRng;
1330
1331 #[test]
1332 #[cfg(feature = "serde")]
1333 fn serde_bincode_basepoint_roundtrip() {
1334 use bincode;
1335
1336 let encoded = bincode::serialize(&constants::RISTRETTO_BASEPOINT_POINT).unwrap();
1337 let enc_compressed =
1338 bincode::serialize(&constants::RISTRETTO_BASEPOINT_COMPRESSED).unwrap();
1339 assert_eq!(encoded, enc_compressed);
1340
1341 assert_eq!(encoded.len(), 32);
1343
1344 let dec_uncompressed: RistrettoPoint = bincode::deserialize(&encoded).unwrap();
1345 let dec_compressed: CompressedRistretto = bincode::deserialize(&encoded).unwrap();
1346
1347 assert_eq!(dec_uncompressed, constants::RISTRETTO_BASEPOINT_POINT);
1348 assert_eq!(dec_compressed, constants::RISTRETTO_BASEPOINT_COMPRESSED);
1349
1350 let raw_bytes = constants::RISTRETTO_BASEPOINT_COMPRESSED.as_bytes();
1352 let bp: RistrettoPoint = bincode::deserialize(raw_bytes).unwrap();
1353 assert_eq!(bp, constants::RISTRETTO_BASEPOINT_POINT);
1354 }
1355
1356 #[test]
1357 fn scalarmult_ristrettopoint_works_both_ways() {
1358 let P = constants::RISTRETTO_BASEPOINT_POINT;
1359 let s = Scalar::from(999u64);
1360
1361 let P1 = P * s;
1362 let P2 = s * P;
1363
1364 assert!(P1.compress().as_bytes() == P2.compress().as_bytes());
1365 }
1366
1367 #[test]
1368 #[cfg(feature = "alloc")]
1369 fn impl_sum() {
1370 let BASE = constants::RISTRETTO_BASEPOINT_POINT;
1372
1373 let s1 = Scalar::from(999u64);
1374 let P1 = BASE * s1;
1375
1376 let s2 = Scalar::from(333u64);
1377 let P2 = BASE * s2;
1378
1379 let vec = vec![P1, P2];
1380 let sum: RistrettoPoint = vec.iter().sum();
1381
1382 assert_eq!(sum, P1 + P2);
1383
1384 let empty_vector: Vec<RistrettoPoint> = vec![];
1386 let sum: RistrettoPoint = empty_vector.iter().sum();
1387
1388 assert_eq!(sum, RistrettoPoint::identity());
1389
1390 let s = Scalar::from(2u64);
1392 let mapped = vec.iter().map(|x| x * s);
1393 let sum: RistrettoPoint = mapped.sum();
1394
1395 assert_eq!(sum, P1 * s + P2 * s);
1396 }
1397
1398 #[test]
1399 fn decompress_negative_s_fails() {
1400 let bad_compressed = CompressedRistretto(constants::EDWARDS_D.to_bytes());
1402 assert!(bad_compressed.decompress().is_none());
1403 }
1404
1405 #[test]
1406 fn decompress_id() {
1407 let compressed_id = CompressedRistretto::identity();
1408 let id = compressed_id.decompress().unwrap();
1409 let mut identity_in_coset = false;
1410 for P in &id.coset4() {
1411 if P.compress() == CompressedEdwardsY::identity() {
1412 identity_in_coset = true;
1413 }
1414 }
1415 assert!(identity_in_coset);
1416 }
1417
1418 #[test]
1419 fn compress_id() {
1420 let id = RistrettoPoint::identity();
1421 assert_eq!(id.compress(), CompressedRistretto::identity());
1422 }
1423
1424 #[test]
1425 fn basepoint_roundtrip() {
1426 let bp_compressed_ristretto = constants::RISTRETTO_BASEPOINT_POINT.compress();
1427 let bp_recaf = bp_compressed_ristretto.decompress().unwrap().0;
1428 let diff = constants::RISTRETTO_BASEPOINT_POINT.0 - bp_recaf;
1430 let diff4 = diff.mul_by_pow_2(2);
1431 assert_eq!(diff4.compress(), CompressedEdwardsY::identity());
1432 }
1433
1434 #[test]
1435 fn encodings_of_small_multiples_of_basepoint() {
1436 let compressed = [
1439 CompressedRistretto([
1440 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1441 0, 0, 0, 0,
1442 ]),
1443 CompressedRistretto([
1444 226, 242, 174, 10, 106, 188, 78, 113, 168, 132, 169, 97, 197, 0, 81, 95, 88, 227,
1445 11, 106, 165, 130, 221, 141, 182, 166, 89, 69, 224, 141, 45, 118,
1446 ]),
1447 CompressedRistretto([
1448 106, 73, 50, 16, 247, 73, 156, 209, 127, 236, 181, 16, 174, 12, 234, 35, 161, 16,
1449 232, 213, 185, 1, 248, 172, 173, 211, 9, 92, 115, 163, 185, 25,
1450 ]),
1451 CompressedRistretto([
1452 148, 116, 31, 93, 93, 82, 117, 94, 206, 79, 35, 240, 68, 238, 39, 213, 209, 234,
1453 30, 43, 209, 150, 180, 98, 22, 107, 22, 21, 42, 157, 2, 89,
1454 ]),
1455 CompressedRistretto([
1456 218, 128, 134, 39, 115, 53, 139, 70, 111, 250, 223, 224, 179, 41, 58, 179, 217,
1457 253, 83, 197, 234, 108, 149, 83, 88, 245, 104, 50, 45, 175, 106, 87,
1458 ]),
1459 CompressedRistretto([
1460 232, 130, 177, 49, 1, 107, 82, 193, 211, 51, 112, 128, 24, 124, 247, 104, 66, 62,
1461 252, 203, 181, 23, 187, 73, 90, 184, 18, 196, 22, 15, 244, 78,
1462 ]),
1463 CompressedRistretto([
1464 246, 71, 70, 211, 201, 43, 19, 5, 14, 216, 216, 2, 54, 167, 240, 0, 124, 59, 63,
1465 150, 47, 91, 167, 147, 209, 154, 96, 30, 187, 29, 244, 3,
1466 ]),
1467 CompressedRistretto([
1468 68, 245, 53, 32, 146, 110, 200, 31, 189, 90, 56, 120, 69, 190, 183, 223, 133, 169,
1469 106, 36, 236, 225, 135, 56, 189, 207, 166, 167, 130, 42, 23, 109,
1470 ]),
1471 CompressedRistretto([
1472 144, 50, 147, 216, 242, 40, 126, 190, 16, 226, 55, 77, 193, 165, 62, 11, 200, 135,
1473 229, 146, 105, 159, 2, 208, 119, 213, 38, 60, 221, 85, 96, 28,
1474 ]),
1475 CompressedRistretto([
1476 2, 98, 42, 206, 143, 115, 3, 163, 28, 175, 198, 63, 143, 196, 143, 220, 22, 225,
1477 200, 200, 210, 52, 178, 240, 214, 104, 82, 130, 169, 7, 96, 49,
1478 ]),
1479 CompressedRistretto([
1480 32, 112, 111, 215, 136, 178, 114, 10, 30, 210, 165, 218, 212, 149, 43, 1, 244, 19,
1481 188, 240, 231, 86, 77, 232, 205, 200, 22, 104, 158, 45, 185, 95,
1482 ]),
1483 CompressedRistretto([
1484 188, 232, 63, 139, 165, 221, 47, 165, 114, 134, 76, 36, 186, 24, 16, 249, 82, 43,
1485 198, 0, 74, 254, 149, 135, 122, 199, 50, 65, 202, 253, 171, 66,
1486 ]),
1487 CompressedRistretto([
1488 228, 84, 158, 225, 107, 154, 160, 48, 153, 202, 32, 140, 103, 173, 175, 202, 250,
1489 76, 63, 62, 78, 83, 3, 222, 96, 38, 227, 202, 143, 248, 68, 96,
1490 ]),
1491 CompressedRistretto([
1492 170, 82, 224, 0, 223, 46, 22, 245, 95, 177, 3, 47, 195, 59, 196, 39, 66, 218, 214,
1493 189, 90, 143, 192, 190, 1, 103, 67, 108, 89, 72, 80, 31,
1494 ]),
1495 CompressedRistretto([
1496 70, 55, 107, 128, 244, 9, 178, 157, 194, 181, 246, 240, 197, 37, 145, 153, 8, 150,
1497 229, 113, 111, 65, 71, 124, 211, 0, 133, 171, 127, 16, 48, 30,
1498 ]),
1499 CompressedRistretto([
1500 224, 196, 24, 247, 200, 217, 196, 205, 215, 57, 91, 147, 234, 18, 79, 58, 217, 144,
1501 33, 187, 104, 29, 252, 51, 2, 169, 217, 154, 46, 83, 230, 78,
1502 ]),
1503 ];
1504 let mut bp = RistrettoPoint::identity();
1505 for point in compressed {
1506 assert_eq!(bp.compress(), point);
1507 bp += constants::RISTRETTO_BASEPOINT_POINT;
1508 }
1509 }
1510
1511 #[test]
1512 fn four_torsion_basepoint() {
1513 let bp = constants::RISTRETTO_BASEPOINT_POINT;
1514 let bp_coset = bp.coset4();
1515 for point in bp_coset {
1516 assert_eq!(bp, RistrettoPoint(point));
1517 }
1518 }
1519
1520 #[test]
1521 fn four_torsion_random() {
1522 let mut rng = OsRng;
1523 let P = RistrettoPoint::mul_base(&Scalar::random(&mut rng));
1524 let P_coset = P.coset4();
1525 for point in P_coset {
1526 assert_eq!(P, RistrettoPoint(point));
1527 }
1528 }
1529
1530 #[test]
1531 fn elligator_vs_ristretto_sage() {
1532 let bytes: [[u8; 32]; 16] = [
1539 [
1540 184, 249, 135, 49, 253, 123, 89, 113, 67, 160, 6, 239, 7, 105, 211, 41, 192, 249,
1541 185, 57, 9, 102, 70, 198, 15, 127, 7, 26, 160, 102, 134, 71,
1542 ],
1543 [
1544 229, 14, 241, 227, 75, 9, 118, 60, 128, 153, 226, 21, 183, 217, 91, 136, 98, 0,
1545 231, 156, 124, 77, 82, 139, 142, 134, 164, 169, 169, 62, 250, 52,
1546 ],
1547 [
1548 115, 109, 36, 220, 180, 223, 99, 6, 204, 169, 19, 29, 169, 68, 84, 23, 21, 109,
1549 189, 149, 127, 205, 91, 102, 172, 35, 112, 35, 134, 69, 186, 34,
1550 ],
1551 [
1552 16, 49, 96, 107, 171, 199, 164, 9, 129, 16, 64, 62, 241, 63, 132, 173, 209, 160,
1553 112, 215, 105, 50, 157, 81, 253, 105, 1, 154, 229, 25, 120, 83,
1554 ],
1555 [
1556 156, 131, 161, 162, 236, 251, 5, 187, 167, 171, 17, 178, 148, 210, 90, 207, 86, 21,
1557 79, 161, 167, 215, 234, 1, 136, 242, 182, 248, 38, 85, 79, 86,
1558 ],
1559 [
1560 251, 177, 124, 54, 18, 101, 75, 235, 245, 186, 19, 46, 133, 157, 229, 64, 10, 136,
1561 181, 185, 78, 144, 254, 167, 137, 49, 107, 10, 61, 10, 21, 25,
1562 ],
1563 [
1564 232, 193, 20, 68, 240, 77, 186, 77, 183, 40, 44, 86, 150, 31, 198, 212, 76, 81, 3,
1565 217, 197, 8, 126, 128, 126, 152, 164, 208, 153, 44, 189, 77,
1566 ],
1567 [
1568 173, 229, 149, 177, 37, 230, 30, 69, 61, 56, 172, 190, 219, 115, 167, 194, 71, 134,
1569 59, 75, 28, 244, 118, 26, 162, 97, 64, 16, 15, 189, 30, 64,
1570 ],
1571 [
1572 106, 71, 61, 107, 250, 117, 42, 151, 91, 202, 212, 100, 52, 188, 190, 21, 125, 218,
1573 31, 18, 253, 241, 160, 133, 57, 242, 3, 164, 189, 68, 111, 75,
1574 ],
1575 [
1576 112, 204, 182, 90, 220, 198, 120, 73, 173, 107, 193, 17, 227, 40, 162, 36, 150,
1577 141, 235, 55, 172, 183, 12, 39, 194, 136, 43, 153, 244, 118, 91, 89,
1578 ],
1579 [
1580 111, 24, 203, 123, 254, 189, 11, 162, 51, 196, 163, 136, 204, 143, 10, 222, 33,
1581 112, 81, 205, 34, 35, 8, 66, 90, 6, 164, 58, 170, 177, 34, 25,
1582 ],
1583 [
1584 225, 183, 30, 52, 236, 82, 6, 183, 109, 25, 227, 181, 25, 82, 41, 193, 80, 77, 161,
1585 80, 242, 203, 79, 204, 136, 245, 131, 110, 237, 106, 3, 58,
1586 ],
1587 [
1588 207, 246, 38, 56, 30, 86, 176, 90, 27, 200, 61, 42, 221, 27, 56, 210, 79, 178, 189,
1589 120, 68, 193, 120, 167, 77, 185, 53, 197, 124, 128, 191, 126,
1590 ],
1591 [
1592 1, 136, 215, 80, 240, 46, 63, 147, 16, 244, 230, 207, 82, 189, 74, 50, 106, 169,
1593 138, 86, 30, 131, 214, 202, 166, 125, 251, 228, 98, 24, 36, 21,
1594 ],
1595 [
1596 210, 207, 228, 56, 155, 116, 207, 54, 84, 195, 251, 215, 249, 199, 116, 75, 109,
1597 239, 196, 251, 194, 246, 252, 228, 70, 146, 156, 35, 25, 39, 241, 4,
1598 ],
1599 [
1600 34, 116, 123, 9, 8, 40, 93, 189, 9, 103, 57, 103, 66, 227, 3, 2, 157, 107, 134,
1601 219, 202, 74, 230, 154, 78, 107, 219, 195, 214, 14, 84, 80,
1602 ],
1603 ];
1604 let encoded_images: [CompressedRistretto; 16] = [
1605 CompressedRistretto([
1606 176, 157, 237, 97, 66, 29, 140, 166, 168, 94, 26, 157, 212, 216, 229, 160, 195,
1607 246, 232, 239, 169, 112, 63, 193, 64, 32, 152, 69, 11, 190, 246, 86,
1608 ]),
1609 CompressedRistretto([
1610 234, 141, 77, 203, 181, 225, 250, 74, 171, 62, 15, 118, 78, 212, 150, 19, 131, 14,
1611 188, 238, 194, 244, 141, 138, 166, 162, 83, 122, 228, 201, 19, 26,
1612 ]),
1613 CompressedRistretto([
1614 232, 231, 51, 92, 5, 168, 80, 36, 173, 179, 104, 68, 186, 149, 68, 40, 140, 170,
1615 27, 103, 99, 140, 21, 242, 43, 62, 250, 134, 208, 255, 61, 89,
1616 ]),
1617 CompressedRistretto([
1618 208, 120, 140, 129, 177, 179, 237, 159, 252, 160, 28, 13, 206, 5, 211, 241, 192,
1619 218, 1, 97, 130, 241, 20, 169, 119, 46, 246, 29, 79, 80, 77, 84,
1620 ]),
1621 CompressedRistretto([
1622 202, 11, 236, 145, 58, 12, 181, 157, 209, 6, 213, 88, 75, 147, 11, 119, 191, 139,
1623 47, 142, 33, 36, 153, 193, 223, 183, 178, 8, 205, 120, 248, 110,
1624 ]),
1625 CompressedRistretto([
1626 26, 66, 231, 67, 203, 175, 116, 130, 32, 136, 62, 253, 215, 46, 5, 214, 166, 248,
1627 108, 237, 216, 71, 244, 173, 72, 133, 82, 6, 143, 240, 104, 41,
1628 ]),
1629 CompressedRistretto([
1630 40, 157, 102, 96, 201, 223, 200, 197, 150, 181, 106, 83, 103, 126, 143, 33, 145,
1631 230, 78, 6, 171, 146, 210, 143, 112, 5, 245, 23, 183, 138, 18, 120,
1632 ]),
1633 CompressedRistretto([
1634 220, 37, 27, 203, 239, 196, 176, 131, 37, 66, 188, 243, 185, 250, 113, 23, 167,
1635 211, 154, 243, 168, 215, 54, 171, 159, 36, 195, 81, 13, 150, 43, 43,
1636 ]),
1637 CompressedRistretto([
1638 232, 121, 176, 222, 183, 196, 159, 90, 238, 193, 105, 52, 101, 167, 244, 170, 121,
1639 114, 196, 6, 67, 152, 80, 185, 221, 7, 83, 105, 176, 208, 224, 121,
1640 ]),
1641 CompressedRistretto([
1642 226, 181, 183, 52, 241, 163, 61, 179, 221, 207, 220, 73, 245, 242, 25, 236, 67, 84,
1643 179, 222, 167, 62, 167, 182, 32, 9, 92, 30, 165, 127, 204, 68,
1644 ]),
1645 CompressedRistretto([
1646 226, 119, 16, 242, 200, 139, 240, 87, 11, 222, 92, 146, 156, 243, 46, 119, 65, 59,
1647 1, 248, 92, 183, 50, 175, 87, 40, 206, 53, 208, 220, 148, 13,
1648 ]),
1649 CompressedRistretto([
1650 70, 240, 79, 112, 54, 157, 228, 146, 74, 122, 216, 88, 232, 62, 158, 13, 14, 146,
1651 115, 117, 176, 222, 90, 225, 244, 23, 94, 190, 150, 7, 136, 96,
1652 ]),
1653 CompressedRistretto([
1654 22, 71, 241, 103, 45, 193, 195, 144, 183, 101, 154, 50, 39, 68, 49, 110, 51, 44,
1655 62, 0, 229, 113, 72, 81, 168, 29, 73, 106, 102, 40, 132, 24,
1656 ]),
1657 CompressedRistretto([
1658 196, 133, 107, 11, 130, 105, 74, 33, 204, 171, 133, 221, 174, 193, 241, 36, 38,
1659 179, 196, 107, 219, 185, 181, 253, 228, 47, 155, 42, 231, 73, 41, 78,
1660 ]),
1661 CompressedRistretto([
1662 58, 255, 225, 197, 115, 208, 160, 143, 39, 197, 82, 69, 143, 235, 92, 170, 74, 40,
1663 57, 11, 171, 227, 26, 185, 217, 207, 90, 185, 197, 190, 35, 60,
1664 ]),
1665 CompressedRistretto([
1666 88, 43, 92, 118, 223, 136, 105, 145, 238, 186, 115, 8, 214, 112, 153, 253, 38, 108,
1667 205, 230, 157, 130, 11, 66, 101, 85, 253, 110, 110, 14, 148, 112,
1668 ]),
1669 ];
1670 for i in 0..16 {
1671 let r_0 = FieldElement::from_bytes(&bytes[i]);
1672 let Q = RistrettoPoint::elligator_ristretto_flavor(&r_0);
1673 assert_eq!(Q.compress(), encoded_images[i]);
1674 }
1675 }
1676
1677 #[test]
1679 fn one_way_map() {
1680 let test_vectors: &[([u8; 64], CompressedRistretto)] = &[
1683 (
1684 [
1685 0x5d, 0x1b, 0xe0, 0x9e, 0x3d, 0x0c, 0x82, 0xfc, 0x53, 0x81, 0x12, 0x49, 0x0e,
1686 0x35, 0x70, 0x19, 0x79, 0xd9, 0x9e, 0x06, 0xca, 0x3e, 0x2b, 0x5b, 0x54, 0xbf,
1687 0xfe, 0x8b, 0x4d, 0xc7, 0x72, 0xc1, 0x4d, 0x98, 0xb6, 0x96, 0xa1, 0xbb, 0xfb,
1688 0x5c, 0xa3, 0x2c, 0x43, 0x6c, 0xc6, 0x1c, 0x16, 0x56, 0x37, 0x90, 0x30, 0x6c,
1689 0x79, 0xea, 0xca, 0x77, 0x05, 0x66, 0x8b, 0x47, 0xdf, 0xfe, 0x5b, 0xb6,
1690 ],
1691 CompressedRistretto([
1692 0x30, 0x66, 0xf8, 0x2a, 0x1a, 0x74, 0x7d, 0x45, 0x12, 0x0d, 0x17, 0x40, 0xf1,
1693 0x43, 0x58, 0x53, 0x1a, 0x8f, 0x04, 0xbb, 0xff, 0xe6, 0xa8, 0x19, 0xf8, 0x6d,
1694 0xfe, 0x50, 0xf4, 0x4a, 0x0a, 0x46,
1695 ]),
1696 ),
1697 (
1698 [
1699 0xf1, 0x16, 0xb3, 0x4b, 0x8f, 0x17, 0xce, 0xb5, 0x6e, 0x87, 0x32, 0xa6, 0x0d,
1700 0x91, 0x3d, 0xd1, 0x0c, 0xce, 0x47, 0xa6, 0xd5, 0x3b, 0xee, 0x92, 0x04, 0xbe,
1701 0x8b, 0x44, 0xf6, 0x67, 0x8b, 0x27, 0x01, 0x02, 0xa5, 0x69, 0x02, 0xe2, 0x48,
1702 0x8c, 0x46, 0x12, 0x0e, 0x92, 0x76, 0xcf, 0xe5, 0x46, 0x38, 0x28, 0x6b, 0x9e,
1703 0x4b, 0x3c, 0xdb, 0x47, 0x0b, 0x54, 0x2d, 0x46, 0xc2, 0x06, 0x8d, 0x38,
1704 ],
1705 CompressedRistretto([
1706 0xf2, 0x6e, 0x5b, 0x6f, 0x7d, 0x36, 0x2d, 0x2d, 0x2a, 0x94, 0xc5, 0xd0, 0xe7,
1707 0x60, 0x2c, 0xb4, 0x77, 0x3c, 0x95, 0xa2, 0xe5, 0xc3, 0x1a, 0x64, 0xf1, 0x33,
1708 0x18, 0x9f, 0xa7, 0x6e, 0xd6, 0x1b,
1709 ]),
1710 ),
1711 (
1712 [
1713 0x84, 0x22, 0xe1, 0xbb, 0xda, 0xab, 0x52, 0x93, 0x8b, 0x81, 0xfd, 0x60, 0x2e,
1714 0xff, 0xb6, 0xf8, 0x91, 0x10, 0xe1, 0xe5, 0x72, 0x08, 0xad, 0x12, 0xd9, 0xad,
1715 0x76, 0x7e, 0x2e, 0x25, 0x51, 0x0c, 0x27, 0x14, 0x07, 0x75, 0xf9, 0x33, 0x70,
1716 0x88, 0xb9, 0x82, 0xd8, 0x3d, 0x7f, 0xcf, 0x0b, 0x2f, 0xa1, 0xed, 0xff, 0xe5,
1717 0x19, 0x52, 0xcb, 0xe7, 0x36, 0x5e, 0x95, 0xc8, 0x6e, 0xaf, 0x32, 0x5c,
1718 ],
1719 CompressedRistretto([
1720 0x00, 0x6c, 0xcd, 0x2a, 0x9e, 0x68, 0x67, 0xe6, 0xa2, 0xc5, 0xce, 0xa8, 0x3d,
1721 0x33, 0x02, 0xcc, 0x9d, 0xe1, 0x28, 0xdd, 0x2a, 0x9a, 0x57, 0xdd, 0x8e, 0xe7,
1722 0xb9, 0xd7, 0xff, 0xe0, 0x28, 0x26,
1723 ]),
1724 ),
1725 (
1726 [
1727 0xac, 0x22, 0x41, 0x51, 0x29, 0xb6, 0x14, 0x27, 0xbf, 0x46, 0x4e, 0x17, 0xba,
1728 0xee, 0x8d, 0xb6, 0x59, 0x40, 0xc2, 0x33, 0xb9, 0x8a, 0xfc, 0xe8, 0xd1, 0x7c,
1729 0x57, 0xbe, 0xeb, 0x78, 0x76, 0xc2, 0x15, 0x0d, 0x15, 0xaf, 0x1c, 0xb1, 0xfb,
1730 0x82, 0x4b, 0xbd, 0x14, 0x95, 0x5f, 0x2b, 0x57, 0xd0, 0x8d, 0x38, 0x8a, 0xab,
1731 0x43, 0x1a, 0x39, 0x1c, 0xfc, 0x33, 0xd5, 0xba, 0xfb, 0x5d, 0xbb, 0xaf,
1732 ],
1733 CompressedRistretto([
1734 0xf8, 0xf0, 0xc8, 0x7c, 0xf2, 0x37, 0x95, 0x3c, 0x58, 0x90, 0xae, 0xc3, 0x99,
1735 0x81, 0x69, 0x00, 0x5d, 0xae, 0x3e, 0xca, 0x1f, 0xbb, 0x04, 0x54, 0x8c, 0x63,
1736 0x59, 0x53, 0xc8, 0x17, 0xf9, 0x2a,
1737 ]),
1738 ),
1739 (
1740 [
1741 0x16, 0x5d, 0x69, 0x7a, 0x1e, 0xf3, 0xd5, 0xcf, 0x3c, 0x38, 0x56, 0x5b, 0xee,
1742 0xfc, 0xf8, 0x8c, 0x0f, 0x28, 0x2b, 0x8e, 0x7d, 0xbd, 0x28, 0x54, 0x4c, 0x48,
1743 0x34, 0x32, 0xf1, 0xce, 0xc7, 0x67, 0x5d, 0xeb, 0xea, 0x8e, 0xbb, 0x4e, 0x5f,
1744 0xe7, 0xd6, 0xf6, 0xe5, 0xdb, 0x15, 0xf1, 0x55, 0x87, 0xac, 0x4d, 0x4d, 0x4a,
1745 0x1d, 0xe7, 0x19, 0x1e, 0x0c, 0x1c, 0xa6, 0x66, 0x4a, 0xbc, 0xc4, 0x13,
1746 ],
1747 CompressedRistretto([
1748 0xae, 0x81, 0xe7, 0xde, 0xdf, 0x20, 0xa4, 0x97, 0xe1, 0x0c, 0x30, 0x4a, 0x76,
1749 0x5c, 0x17, 0x67, 0xa4, 0x2d, 0x6e, 0x06, 0x02, 0x97, 0x58, 0xd2, 0xd7, 0xe8,
1750 0xef, 0x7c, 0xc4, 0xc4, 0x11, 0x79,
1751 ]),
1752 ),
1753 (
1754 [
1755 0xa8, 0x36, 0xe6, 0xc9, 0xa9, 0xca, 0x9f, 0x1e, 0x8d, 0x48, 0x62, 0x73, 0xad,
1756 0x56, 0xa7, 0x8c, 0x70, 0xcf, 0x18, 0xf0, 0xce, 0x10, 0xab, 0xb1, 0xc7, 0x17,
1757 0x2d, 0xdd, 0x60, 0x5d, 0x7f, 0xd2, 0x97, 0x98, 0x54, 0xf4, 0x7a, 0xe1, 0xcc,
1758 0xf2, 0x04, 0xa3, 0x31, 0x02, 0x09, 0x5b, 0x42, 0x00, 0xe5, 0xbe, 0xfc, 0x04,
1759 0x65, 0xac, 0xcc, 0x26, 0x31, 0x75, 0x48, 0x5f, 0x0e, 0x17, 0xea, 0x5c,
1760 ],
1761 CompressedRistretto([
1762 0xe2, 0x70, 0x56, 0x52, 0xff, 0x9f, 0x5e, 0x44, 0xd3, 0xe8, 0x41, 0xbf, 0x1c,
1763 0x25, 0x1c, 0xf7, 0xdd, 0xdb, 0x77, 0xd1, 0x40, 0x87, 0x0d, 0x1a, 0xb2, 0xed,
1764 0x64, 0xf1, 0xa9, 0xce, 0x86, 0x28,
1765 ]),
1766 ),
1767 (
1768 [
1769 0x2c, 0xdc, 0x11, 0xea, 0xeb, 0x95, 0xda, 0xf0, 0x11, 0x89, 0x41, 0x7c, 0xdd,
1770 0xdb, 0xf9, 0x59, 0x52, 0x99, 0x3a, 0xa9, 0xcb, 0x9c, 0x64, 0x0e, 0xb5, 0x05,
1771 0x8d, 0x09, 0x70, 0x2c, 0x74, 0x62, 0x2c, 0x99, 0x65, 0xa6, 0x97, 0xa3, 0xb3,
1772 0x45, 0xec, 0x24, 0xee, 0x56, 0x33, 0x5b, 0x55, 0x6e, 0x67, 0x7b, 0x30, 0xe6,
1773 0xf9, 0x0a, 0xc7, 0x7d, 0x78, 0x10, 0x64, 0xf8, 0x66, 0xa3, 0xc9, 0x82,
1774 ],
1775 CompressedRistretto([
1776 0x80, 0xbd, 0x07, 0x26, 0x25, 0x11, 0xcd, 0xde, 0x48, 0x63, 0xf8, 0xa7, 0x43,
1777 0x4c, 0xef, 0x69, 0x67, 0x50, 0x68, 0x1c, 0xb9, 0x51, 0x0e, 0xea, 0x55, 0x70,
1778 0x88, 0xf7, 0x6d, 0x9e, 0x50, 0x65,
1779 ]),
1780 ),
1781 (
1782 [
1783 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1784 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1785 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1786 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1787 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1788 ],
1789 CompressedRistretto([
1790 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
1791 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
1792 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
1793 ]),
1794 ),
1795 (
1796 [
1797 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1798 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1799 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1800 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1801 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1802 ],
1803 CompressedRistretto([
1804 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
1805 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
1806 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
1807 ]),
1808 ),
1809 (
1810 [
1811 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1812 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1813 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1814 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1815 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
1816 ],
1817 CompressedRistretto([
1818 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
1819 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
1820 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
1821 ]),
1822 ),
1823 (
1824 [
1825 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1826 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1827 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1828 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1829 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
1830 ],
1831 CompressedRistretto([
1832 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
1833 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
1834 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
1835 ]),
1836 ),
1837 ];
1838 for (input, output) in test_vectors {
1840 let Q = RistrettoPoint::from_uniform_bytes(input);
1841 assert_eq!(&Q.compress(), output);
1842 }
1843 }
1844
1845 #[test]
1846 fn random_roundtrip() {
1847 let mut rng = OsRng;
1848 for _ in 0..100 {
1849 let P = RistrettoPoint::mul_base(&Scalar::random(&mut rng));
1850 let compressed_P = P.compress();
1851 let Q = compressed_P.decompress().unwrap();
1852 assert_eq!(P, Q);
1853 }
1854 }
1855
1856 #[test]
1857 #[cfg(all(feature = "alloc", feature = "rand_core"))]
1858 fn double_and_compress_1024_random_points() {
1859 let mut rng = OsRng;
1860
1861 let mut points: Vec<RistrettoPoint> = (0..1024)
1862 .map(|_| RistrettoPoint::random(&mut rng))
1863 .collect();
1864 points[500] = RistrettoPoint::identity();
1865
1866 let compressed = RistrettoPoint::double_and_compress_batch(&points);
1867
1868 for (P, P2_compressed) in points.iter().zip(compressed.iter()) {
1869 assert_eq!(*P2_compressed, (P + P).compress());
1870 }
1871 }
1872
1873 #[test]
1874 #[cfg(feature = "alloc")]
1875 fn vartime_precomputed_vs_nonprecomputed_multiscalar() {
1876 let mut rng = rand::thread_rng();
1877
1878 let static_scalars = (0..128)
1879 .map(|_| Scalar::random(&mut rng))
1880 .collect::<Vec<_>>();
1881
1882 let dynamic_scalars = (0..128)
1883 .map(|_| Scalar::random(&mut rng))
1884 .collect::<Vec<_>>();
1885
1886 let check_scalar: Scalar = static_scalars
1887 .iter()
1888 .chain(dynamic_scalars.iter())
1889 .map(|s| s * s)
1890 .sum();
1891
1892 let static_points = static_scalars
1893 .iter()
1894 .map(RistrettoPoint::mul_base)
1895 .collect::<Vec<_>>();
1896 let dynamic_points = dynamic_scalars
1897 .iter()
1898 .map(RistrettoPoint::mul_base)
1899 .collect::<Vec<_>>();
1900
1901 let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
1902
1903 assert_eq!(precomputation.len(), 128);
1904 assert!(!precomputation.is_empty());
1905
1906 let P = precomputation.vartime_mixed_multiscalar_mul(
1907 &static_scalars,
1908 &dynamic_scalars,
1909 &dynamic_points,
1910 );
1911
1912 use crate::traits::VartimeMultiscalarMul;
1913 let Q = RistrettoPoint::vartime_multiscalar_mul(
1914 static_scalars.iter().chain(dynamic_scalars.iter()),
1915 static_points.iter().chain(dynamic_points.iter()),
1916 );
1917
1918 let R = RistrettoPoint::mul_base(&check_scalar);
1919
1920 assert_eq!(P.compress(), R.compress());
1921 assert_eq!(Q.compress(), R.compress());
1922 }
1923
1924 #[test]
1925 #[cfg(feature = "alloc")]
1926 fn partial_precomputed_mixed_multiscalar_empty() {
1927 let mut rng = rand::thread_rng();
1928
1929 let n_static = 16;
1930 let n_dynamic = 8;
1931
1932 let static_points = (0..n_static)
1933 .map(|_| RistrettoPoint::random(&mut rng))
1934 .collect::<Vec<_>>();
1935
1936 let static_scalars = Vec::new();
1938
1939 let dynamic_points = (0..n_dynamic)
1940 .map(|_| RistrettoPoint::random(&mut rng))
1941 .collect::<Vec<_>>();
1942
1943 let dynamic_scalars = (0..n_dynamic)
1944 .map(|_| Scalar::random(&mut rng))
1945 .collect::<Vec<_>>();
1946
1947 let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
1949 let result_multiscalar = precomputation.vartime_mixed_multiscalar_mul(
1950 &static_scalars,
1951 &dynamic_scalars,
1952 &dynamic_points,
1953 );
1954
1955 let mut result_manual = RistrettoPoint::identity();
1957 for i in 0..static_scalars.len() {
1958 result_manual += static_points[i] * static_scalars[i];
1959 }
1960 for i in 0..n_dynamic {
1961 result_manual += dynamic_points[i] * dynamic_scalars[i];
1962 }
1963
1964 assert_eq!(result_multiscalar, result_manual);
1965 }
1966
1967 #[test]
1968 #[cfg(feature = "alloc")]
1969 fn partial_precomputed_mixed_multiscalar() {
1970 let mut rng = rand::thread_rng();
1971
1972 let n_static = 16;
1973 let n_dynamic = 8;
1974
1975 let static_points = (0..n_static)
1976 .map(|_| RistrettoPoint::random(&mut rng))
1977 .collect::<Vec<_>>();
1978
1979 let static_scalars = (0..n_static - 1)
1981 .map(|_| Scalar::random(&mut rng))
1982 .collect::<Vec<_>>();
1983
1984 let dynamic_points = (0..n_dynamic)
1985 .map(|_| RistrettoPoint::random(&mut rng))
1986 .collect::<Vec<_>>();
1987
1988 let dynamic_scalars = (0..n_dynamic)
1989 .map(|_| Scalar::random(&mut rng))
1990 .collect::<Vec<_>>();
1991
1992 let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
1994 let result_multiscalar = precomputation.vartime_mixed_multiscalar_mul(
1995 &static_scalars,
1996 &dynamic_scalars,
1997 &dynamic_points,
1998 );
1999
2000 let mut result_manual = RistrettoPoint::identity();
2002 for i in 0..static_scalars.len() {
2003 result_manual += static_points[i] * static_scalars[i];
2004 }
2005 for i in 0..n_dynamic {
2006 result_manual += dynamic_points[i] * dynamic_scalars[i];
2007 }
2008
2009 assert_eq!(result_multiscalar, result_manual);
2010 }
2011
2012 #[test]
2013 #[cfg(feature = "alloc")]
2014 fn partial_precomputed_multiscalar() {
2015 let mut rng = rand::thread_rng();
2016
2017 let n_static = 16;
2018
2019 let static_points = (0..n_static)
2020 .map(|_| RistrettoPoint::random(&mut rng))
2021 .collect::<Vec<_>>();
2022
2023 let static_scalars = (0..n_static - 1)
2025 .map(|_| Scalar::random(&mut rng))
2026 .collect::<Vec<_>>();
2027
2028 let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
2030 let result_multiscalar = precomputation.vartime_multiscalar_mul(&static_scalars);
2031
2032 let mut result_manual = RistrettoPoint::identity();
2034 for i in 0..static_scalars.len() {
2035 result_manual += static_points[i] * static_scalars[i];
2036 }
2037
2038 assert_eq!(result_multiscalar, result_manual);
2039 }
2040
2041 #[test]
2042 #[cfg(feature = "alloc")]
2043 fn partial_precomputed_multiscalar_empty() {
2044 let mut rng = rand::thread_rng();
2045
2046 let n_static = 16;
2047
2048 let static_points = (0..n_static)
2049 .map(|_| RistrettoPoint::random(&mut rng))
2050 .collect::<Vec<_>>();
2051
2052 let static_scalars = Vec::new();
2054
2055 let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
2057 let result_multiscalar = precomputation.vartime_multiscalar_mul(&static_scalars);
2058
2059 let mut result_manual = RistrettoPoint::identity();
2061 for i in 0..static_scalars.len() {
2062 result_manual += static_points[i] * static_scalars[i];
2063 }
2064
2065 assert_eq!(result_multiscalar, result_manual);
2066 }
2067}