@@ -513,4 +513,126 @@ ProjPoint<Curve> msm(const typename Curve::uint_type& u, const AffinePoint<Curve
513513 return r;
514514}
515515
516+ template <typename UIntT>
517+ struct SignedScalar
518+ {
519+ bool sign = false ; // The sign of the scalar: false = positive, true = negative.
520+ UIntT value;
521+ };
522+
523+
524+ // / Verifies k ≡ k₁ + k₂·λ (mod N) and checks that k₁ and k₂ are "short" scalars.
525+ template <typename Curve>
526+ [[maybe_unused, nodiscard]] bool verify_scalar_decomposition (const typename Curve::uint_type& k,
527+ const SignedScalar<typename Curve::uint_type>& k1,
528+ const SignedScalar<typename Curve::uint_type>& k2) noexcept
529+ {
530+ // Verify k ≡ k₁ + k₂·λ (mod N).
531+ {
532+ static constexpr ModArith N{Curve::ORDER};
533+ auto r_k1 = N.to_mont (k1.value );
534+ if (k1.sign )
535+ r_k1 = N.sub (0 , r_k1);
536+ auto r_k2 = N.to_mont (k2.value );
537+ if (k2.sign )
538+ r_k2 = N.sub (0 , r_k2);
539+
540+ const auto r_k = N.to_mont (k);
541+
542+ const auto right = N.add (r_k1, N.mul (r_k2, N.to_mont (Curve::LAMBDA)));
543+ if (r_k != right)
544+ return false ;
545+ }
546+
547+ // Verify for u = (k₁, k₂) that ‖u‖ <= max(‖v₁‖, ‖v₂‖).
548+ {
549+ static constexpr auto V1_NORM_SQUARED =
550+ Curve::X1 * Curve::X1 + Curve::MINUS_Y1 * Curve::MINUS_Y1;
551+ static constexpr auto V2_NORM_SQUARED = Curve::X2 * Curve::X2 + Curve::Y2 * Curve::Y2;
552+ static constexpr auto MAX_NORM_SQUARED = std::max (V1_NORM_SQUARED, V2_NORM_SQUARED);
553+ const auto u_norm_squared = k1.value * k1.value + k2.value * k2.value ;
554+ return u_norm_squared <= MAX_NORM_SQUARED;
555+ }
556+ }
557+
558+ // / Decomposes a scalar k into "short" scalars k₁ and k₂ such that k₁ + k₂·λ ≡ k (mod N).
559+ // /
560+ // / This decomposition allows more efficient scalar multiplication by using the multi-scalar
561+ // / multiplication (MSM) and the GLV endomorphism.
562+ // / The endomorphism ϕ: E₂ → E₂ defined as (𝑥,𝑦) → (β𝑥,𝑦) with eigenvalue λ allows computing
563+ // / [λ](𝑥,𝑦) = (β𝑥,𝑦) with only one multiplication in 𝔽ₚ instead of a full scalar multiplication.
564+ // /
565+ // / Moreover, to compute the short scalars k₁ and k₂, we need linearly independent short vectors
566+ // / (𝑣₁=(𝑥₁,𝑦₁), 𝑣₂=(x₂,𝑦₂)) such that f(𝑣₁) = f(𝑣₂) = 0,
567+ // / where f: ℤ×ℤ → ℤₙ is defined as (x,y) → (x + y·λ), where λ² + λ ≡ -1 mod N.
568+ // /
569+ // / See https://www.iacr.org/archive/crypto2001/21390189.pdf for details.
570+ // /
571+ // / The Curve type must provide the endomorphism parameters: LAMBDA, BETA, X1, MINUS_Y1, X2, Y2.
572+ template <typename Curve>
573+ std::array<SignedScalar<typename Curve::uint_type>, 2 > decompose (
574+ const typename Curve::uint_type& k) noexcept
575+ {
576+ using UIntT = Curve::uint_type;
577+
578+ // Validate the provided setup parameters.
579+ // λ² + λ ≡ -1 mod n
580+ static_assert ((umul (Curve::LAMBDA, Curve::LAMBDA) + Curve::LAMBDA + 1 ) % Curve::ORDER == 0 );
581+ // f: (x, y) → (x + λy) mod N
582+ // f(v₁) = 0
583+ static_assert (
584+ (Curve::X1 + umul (Curve::ORDER - Curve::MINUS_Y1, Curve::LAMBDA)) % Curve::ORDER == 0 );
585+ // f(v₂) = 0
586+ static_assert ((Curve::X2 + umul (Curve::Y2, Curve::LAMBDA)) % Curve::ORDER == 0 );
587+
588+ static constexpr auto round_div = [](const auto & a) noexcept {
589+ // DET is the (𝑣₁, 𝑣₂) matrix determinant.
590+ static constexpr auto WIDE_DET =
591+ umul (Curve::X1, Curve::Y2) + umul (Curve::X2, Curve::MINUS_Y1);
592+ static_assert (WIDE_DET <= std::numeric_limits<UIntT>::max ());
593+ static constexpr auto DET = static_cast <UIntT>(WIDE_DET);
594+ static constexpr auto HALF_DET = DET / 2 ;
595+
596+ const auto [wide_q, r] = udivrem (a, DET);
597+ // Division reduces the quotient enough to fit into a single uint.
598+ // This can be shown at compile-time by inspecting the DET and Y2/-Y1 values.
599+ assert (wide_q < std::numeric_limits<UIntT>::max ());
600+ const auto q = static_cast <UIntT>(wide_q);
601+ return q + (r > HALF_DET); // Round to nearest.
602+ };
603+
604+ // Solve a system of two equations using Cramer method.
605+ // ⎡X1 X2⎤ * ⎡b1⎤ = ⎡k⎤
606+ // ⎣Y1 Y2⎦ ⎣b2⎦ ⎣0⎦
607+ // and then approximate to the nearest integers:
608+ // b1 = ⌊ Y2·k ÷ DET⌉
609+ // b2 = ⌊-Y1·k ÷ DET⌉
610+ const auto b1 = round_div (umul (k, Curve::Y2));
611+ const auto b2 = round_div (umul (k, Curve::MINUS_Y1));
612+
613+ // k1 = k - (x1*b1 + x2*b2)
614+ const auto x1b1_x2b2 = umul (b1, Curve::X1) + umul (b2, Curve::X2);
615+ const auto [wide_k1, k1_is_neg] = subc (decltype (x1b1_x2b2){k}, x1b1_x2b2);
616+ const auto k1_abs = k1_is_neg ? -static_cast <UIntT>(wide_k1) : static_cast <UIntT>(wide_k1);
617+
618+ // k2 = 0 - (y1*b1 + y2*b2)
619+ const auto [wide_k2, k2_is_neg] = subc (umul (b1, Curve::MINUS_Y1), umul (b2, Curve::Y2));
620+ const auto k2_abs = k2_is_neg ? -static_cast <UIntT>(wide_k2) : static_cast <UIntT>(wide_k2);
621+
622+ const SignedScalar k1{k1_is_neg, k1_abs};
623+ const SignedScalar k2{k2_is_neg, k2_abs};
624+ assert (verify_scalar_decomposition<Curve>(k, k1, k2));
625+
626+ // FIXME: Bounds for fuzzing, remove.
627+ static_assert (Curve::Y2 < Curve::X1);
628+ static_assert (Curve::X2 < Curve::Y2);
629+ static_assert (Curve::MINUS_Y1 < Curve::X2);
630+ [[maybe_unused]] static constexpr auto K1_MAX = Curve::Y2;
631+ [[maybe_unused]] static constexpr auto K2_MAX = Curve::X2 - 1 ;
632+ assert (k1.value <= K1_MAX);
633+ assert (k2.value <= K2_MAX);
634+
635+ return {k1, k2};
636+ }
637+
516638} // namespace evmmax::ecc
0 commit comments