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
73 changes: 41 additions & 32 deletions ec/src/models/bls12/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,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 +143,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
})
}
}