Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster final exponentiation for BLS12 #211

Merged
merged 12 commits into from
Mar 17, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ The main features of this release are:
- #201 (ark-ec, ark-ff, ark-test-curves, ark-test-templates) Remove the dependency on `rand_xorshift`
- #205 (ark-ec, ark-ff) Unroll loops and conditionally use intrinsics in `biginteger` arithmetic, and reduce copies in `ff` and `ec` arithmetic.
- #207 (ark-ff) Improve performance of extension fields when the non-residue is negative. (Improves fq2, fq12, and g2 speed on bls12 and bn curves)
- #211 (ark-ec) Improve performance of BLS12 final exponentiation.

### Bug fixes
- #36 (ark-ec) In Short-Weierstrass curves, include an infinity bit in `ToConstraintField`.
Expand Down
79 changes: 47 additions & 32 deletions ec/src/models/bls12/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ use num_traits::One;

use core::marker::PhantomData;

/// A particular BLS12 group can have G2 being either a multiplicative or a divisive twist.
pub enum TwistType {
M,
D,
}

pub trait Bls12Parameters: 'static {
/// Parameterizes the BLS12 family.
const X: &'static [u64];
/// Is `Self::X` negative?
const X_IS_NEGATIVE: bool;
/// What kind of twist is this?
const TWIST_TYPE: TwistType;

type Fp: PrimeField + SquareRootField + Into<<Self::Fp as PrimeField>::BigInt>;
type Fp2Params: Fp2Parameters<Fp = Self::Fp>;
type Fp6Params: Fp6Parameters<Fp2Params = Self::Fp2Params>;
Expand Down Expand Up @@ -65,6 +70,7 @@ impl<P: Bls12Parameters> Bls12<P> {
}
}

// Exponentiates `f` by `Self::X`.
fn exp_by_x(mut f: Fp12<P::Fp12Params>) -> Fp12<P::Fp12Params> {
f = f.cyclotomic_exp(P::X);
if P::X_IS_NEGATIVE {
Expand Down Expand Up @@ -122,10 +128,8 @@ impl<P: Bls12Parameters> PairingEngine for Bls12<P> {

fn final_exponentiation(f: &Self::Fqk) -> Option<Self::Fqk> {
// Computing the final exponentation following
// https://eprint.iacr.org/2016/130.pdf.
// We don't use their "faster" formula because it is difficult to make
// it work for curves with odd `P::X`.
// Hence we implement the algorithm from Table 1 below.
// https://eprint.iacr.org/2020/875
// Adapted from the implementation in https://github.com/ConsenSys/gurvy/pull/29

// f1 = r.conjugate() = f^(p^6)
let mut f1 = *f;
Expand All @@ -145,35 +149,46 @@ impl<P: Bls12Parameters> PairingEngine for Bls12<P> {
// r = f^((p^6 - 1)(p^2 + 1))
r *= &f2;

// Hard part of the final exponentation is below:
// From https://eprint.iacr.org/2016/130.pdf, Table 1
// Hard part of the final exponentation:
// t[0].CyclotomicSquare(&result)
let mut y0 = r.cyclotomic_square();
y0.conjugate();

let mut y5 = Self::exp_by_x(r);

let mut y1 = y5.cyclotomic_square();
let mut y3 = y0 * &y5;
y0 = Self::exp_by_x(y3);
let y2 = Self::exp_by_x(y0);
let mut y4 = Self::exp_by_x(y2);
y4 *= &y1;
y1 = Self::exp_by_x(y4);
y3.conjugate();
y1 *= &y3;
y1 *= &r;
y3 = r;
y3.conjugate();
y0 *= &r;
y0.frobenius_map(3);
y4 *= &y3;
y4.frobenius_map(1);
y5 *= &y2;
y5.frobenius_map(2);
y5 *= &y0;
y5 *= &y4;
y5 *= &y1;
y5
// t[1].Expt(&result)
let mut y1 = Self::exp_by_x(r);
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
// t[2].InverseUnitary(&result)
let mut y2 = r;
y2.conjugate();
// t[1].Mul(&t[1], &t[2])
y1 *= y2;
Pratyush marked this conversation as resolved.
Show resolved Hide resolved
// t[2].Expt(&t[1])
y2 = Self::exp_by_x(y1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be wary if copies of Fp12 elements, in my experience the compiler produce a bench of movaps, movups instructions, you might want an out-of-place pow_by_x

Copy link
Member Author

@Pratyush Pratyush Mar 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, you mean that exp_by_x should take y1 by reference? Or do you mean that it should mutate y2 in place?

Copy link

@mratsim mratsim Mar 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean mutate y2 in-place. Does Rust take large stack parameter by reference by default? If not it's needed as well but given that Option and Result work well in Rust I assume it does the right thing.

// t[1].InverseUnitary(&t[1])
y1.conjugate();
// t[1].Mul(&t[1], &t[2])
y1 *= y2;
Pratyush marked this conversation as resolved.
Show resolved Hide resolved
// t[2].Expt(&t[1])
y2 = Self::exp_by_x(y1);
// t[1].Frobenius(&t[1])
y1.frobenius_map(1);
// t[1].Mul(&t[1], &t[2])
y1 *= &y2;
// result.Mul(&result, &t[0])
r *= y0;
Pratyush marked this conversation as resolved.
Show resolved Hide resolved
// t[0].Expt(&t[1])
y0 = Self::exp_by_x(y1);
// t[2].Expt(&t[0])
y2 = Self::exp_by_x(y0);
// t[0].FrobeniusSquare(&t[1])
y0 = y1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto for the copy here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm how do I avoid the copy of y1 here?

Copy link

@mratsim mratsim Mar 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I did it

  # (x+p)
  v1.pow_x(v0)                 # v1 = f^((x-1)².x)
  v0.frobenius_map(v0)         # v0 = f^((x-1)².p)
  v0 *= v1                     # v0 = f^((x-1)².(x+p))

  # + 3
  f *= v2                      # f = f³

  # (x²+p²−1)
  v2.pow_x(v0, invert = false)
  v1.pow_x(v2, invert = false) # v1 = f^((x-1)².(x+p).x²)
  v2.frobenius_map(v0, 2)      # v2 = f^((x-1)².(x+p).p²)
  v0.cyclotomic_inv()          # v0 = f^((x-1)².(x+p).-1)
  v0 *= v1                     # v0 = f^((x-1)².(x+p).(x²-1))
  v0 *= v2                     # v0 = f^((x-1)².(x+p).(x²+p²-1))

  # (x−1)².(x+p).(x²+p²−1) + 3
  f *= v0

y0.frobenius_map(2);
// t[1].InverseUnitary(&t[1])
y1.conjugate();
// t[1].Mul(&t[1], &t[2])
y1 *= y2;
// t[1].Mul(&t[1], &t[0])
y1 *= y0;
// result.Mul(&result, &t[1])
r *= y1;
Pratyush marked this conversation as resolved.
Show resolved Hide resolved
r
})
}
}