constantine/constantine/math/pairing/cyclotomic_subgroup.nim
Mamy Ratsimbazafy 9770b3108c
Fp12 over fp6 (#201)
* introduce sumprod for direct fp6_mul

* change curves -> constants

* forgotten constants

* Full pairing using Fp2->Fp6->Fp12 towering
2022-08-14 09:48:10 +02:00

785 lines
24 KiB
Nim
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../../platforms/abstractions,
../config/curves,
../arithmetic,
../extension_fields,
../isogenies/frobenius
# No exceptions allowed
{.push raises: [].}
# ############################################################
#
# Gϕₙ, Cyclotomic subgroup of Fpⁿ
# with Gϕₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) ≡ 1 (mod pⁿ)}
#
# ############################################################
# - Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions
# Granger, Scott, 2009
# https://eprint.iacr.org/2009/565.pdf
#
# - On the final exponentiation for calculating
# pairings on ordinary elliptic curves
# Scott, Benger, Charlemagne, Perez, Kachisa, 2008
# https://eprint.iacr.org/2008/490.pdf
# 𝔽pⁿ -> Gϕₙ - Mapping to Cyclotomic group
# ----------------------------------------------------------------
func finalExpEasy*[C: static Curve](f: var Fp6[C]) {.meter.} =
## Easy part of the final exponentiation
##
## This maps the result of the Miller loop into the cyclotomic subgroup Gϕ₆
##
## We need to clear the Gₜ cofactor to obtain
## an unique Gₜ representation
## (reminder, Gₜ is a multiplicative group hence we exponentiate by the cofactor)
##
## i.e. Fp⁶ --> (fexp easy) --> Gϕ₆ --> (fexp hard) --> Gₜ
##
## The final exponentiation is fexp = f^((p⁶ - 1) / r)
## It is separated into:
## f^((p⁶ - 1) / r) = (p⁶ - 1) / ϕ₆(p) * ϕ₆(p) / r
##
## with the cyclotomic polynomial ϕ₆(p) = (p²-p+1)
##
## With an embedding degree of 6, the easy part of final exponentiation is
##
## f^(p³1)(p+1)
##
## And properties are
## 0. f^(p³) ≡ conj(f) (mod p⁶) for all f in Fp6
##
## After g = f^(p³1) the result g is on the cyclotomic subgroup
## 1. g^(-1) ≡ g^(p³) (mod p⁶)
## 2. Inversion can be done with conjugate
## 3. g is unitary, its norm |g| (the product of conjugates) is 1
## 4. Squaring has a fast compressed variant.
#
# Proofs:
#
# Fp6 can be defined as a quadratic extension over Fp³
# with g = g₀ + x g₁ with x a quadratic non-residue
#
# with q = p³, q² = p⁶
# The frobenius map f^q ≡ (f₀ + x f₁)^q (mod q²)
# ≡ f₀^q + x^q f₁^q (mod q²)
# ≡ f₀ + x^q f₁ (mod q²)
# ≡ f₀ - x f₁ (mod q²)
# hence
# f^p³ ≡ conj(f) (mod p⁶)
# Q.E.D. of (0)
#
# ----------------
#
# p⁶ - 1 = (p³1)(p³+1) = (p³1)(p+1)(p²-p+1)
# by Fermat's little theorem we have
# f^(p⁶ - 1) ≡ 1 (mod p⁶)
#
# Hence f^(p³1)(p³+1) ≡ 1 (mod p⁶)
#
# We call g = f^(p³1) we have
# g^(p³+1) ≡ 1 (mod p⁶) <=> g^(p³) * g ≡ 1 (mod p⁶)
# hence g^(-1) ≡ g^(p³) (mod p⁶)
# Q.E.D. of (1)
#
# --
#
# From (1) g^(-1) ≡ g^(p³) (mod p⁶) for g = f^(p³1)
# and (0) f^p³ ≡ conj(f) (mod p⁶) for all f in fp12
#
# so g^(-1) ≡ conj(g) (mod p⁶) for g = f^(p³1)
# Q.E.D. of (2)
#
# --
#
# f^(p⁶ - 1) ≡ 1 (mod p⁶) by Fermat's Little Theorem
# f^(p³1)(p³+1) ≡ 1 (mod p⁶)
# g^(p³+1) ≡ 1 (mod p⁶)
# g * g^p³ ≡ 1 (mod p⁶)
# g * conj(g) ≡ 1 (mod p⁶)
# Q.E.D. of (3)
var g {.noinit.}: typeof(f)
g.inv(f) # g = f^-1
conj(f) # f = f^p³
g *= f # g = f^(p³-1)
f.frobenius_map(g) # f = f^((p³-1) p)
f *= g # f = f^((p³-1) (p+1))
func finalExpEasy*[C: static Curve](f: var Fp12[C]) {.meter.} =
## Easy part of the final exponentiation
##
## This maps the result of the Miller loop into the cyclotomic subgroup Gϕ₁₂
##
## We need to clear the Gₜ cofactor to obtain
## an unique Gₜ representation
## (reminder, Gₜ is a multiplicative group hence we exponentiate by the cofactor)
##
## i.e. Fp¹² --> (fexp easy) --> Gϕ₁₂ --> (fexp hard) --> Gₜ
##
## The final exponentiation is fexp = f^((p¹² - 1) / r)
## It is separated into:
## f^((p¹² - 1) / r) = (p¹² - 1) / ϕ₁₂(p) * ϕ₁₂(p) / r
##
## with the cyclotomic polynomial ϕ₁₂(p) = (p⁴-p²+1)
##
## With an embedding degree of 12, the easy part of final exponentiation is
##
## f^(p⁶1)(p²+1)
##
## And properties are
## 0. f^(p⁶) ≡ conj(f) (mod p¹²) for all f in Fp12
##
## After g = f^(p⁶1) the result g is on the cyclotomic subgroup
## 1. g^(-1) ≡ g^(p⁶) (mod p¹²)
## 2. Inversion can be done with conjugate
## 3. g is unitary, its norm |g| (the product of conjugates) is 1
## 4. Squaring has a fast compressed variant.
#
# Proofs:
#
# Fp12 can be defined as a quadratic extension over Fp⁶
# with g = g₀ + x g₁ with x a quadratic non-residue
#
# with q = p⁶, q² = p¹²
# The frobenius map f^q ≡ (f₀ + x f₁)^q (mod q²)
# ≡ f₀^q + x^q f₁^q (mod q²)
# ≡ f₀ + x^q f₁ (mod q²)
# ≡ f₀ - x f₁ (mod q²)
# hence
# f^p⁶ ≡ conj(f) (mod p¹²)
# Q.E.D. of (0)
#
# ----------------
#
# p¹² - 1 = (p⁶1)(p⁶+1) = (p⁶1)(p²+1)(p⁴-p²+1)
# by Fermat's little theorem we have
# f^(p¹² - 1) ≡ 1 (mod p¹²)
#
# Hence f^(p⁶1)(p⁶+1) ≡ 1 (mod p¹²)
#
# We call g = f^(p⁶1) we have
# g^(p⁶+1) ≡ 1 (mod p¹²) <=> g^(p⁶) * g ≡ 1 (mod p¹²)
# hence g^(-1) ≡ g^(p⁶) (mod p¹²)
# Q.E.D. of (1)
#
# --
#
# From (1) g^(-1) ≡ g^(p⁶) (mod p¹²) for g = f^(p⁶1)
# and (0) f^p⁶ ≡ conj(f) (mod p¹²) for all f in fp12
#
# so g^(-1) ≡ conj(g) (mod p¹²) for g = f^(p⁶1)
# Q.E.D. of (2)
#
# --
#
# f^(p¹² - 1) ≡ 1 (mod p¹²) by Fermat's Little Theorem
# f^(p⁶1)(p⁶+1) ≡ 1 (mod p¹²)
# g^(p⁶+1) ≡ 1 (mod p¹²)
# g * g^p⁶ ≡ 1 (mod p¹²)
# g * conj(g) ≡ 1 (mod p¹²)
# Q.E.D. of (3)
var g {.noinit.}: typeof(f)
g.inv(f) # g = f^-1
conj(f) # f = f^p⁶
g *= f # g = f^(p⁶-1)
f.frobenius_map(g, 2) # f = f^((p⁶-1) p²)
f *= g # f = f^((p⁶-1) (p²+1))
# Gϕₙ - Cyclotomic functions
# ----------------------------------------------------------------
# A cyclotomic group is a subgroup of Fpⁿ defined by
#
# Gϕₙ(p) = {α ∈ Fpⁿ : α^Φₙ(p) = 1}
#
# The result of any pairing is in a cyclotomic subgroup
func cyclotomic_inv*[FT](a: var FT) {.meter.} =
## Fast inverse for a
## `a` MUST be in the cyclotomic subgroup
## consequently `a` MUST be unitary
a.conj()
func cyclotomic_inv*[FT](r: var FT, a: FT) {.meter.} =
## Fast inverse for a
## `a` MUST be in the cyclotomic subgroup
## consequently `a` MUST be unitary
r.conj(a)
func cyclotomic_square_cube_over_quad(r: var CubicExt, a: CubicExt) =
## Square `a` into `r`
## `a` MUST be in the cyclotomic subgroup
## consequently `a` MUST be unitary
#
# Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions
# Granger, Scott, 2009
# https://eprint.iacr.org/2009/565.pdf
# Cubic extension field
# A = 3a² 2 ̄a
# B = 3 √i c² + 2 ̄b
# C = 3b² 2 ̄c
var v0{.noInit.}, v1{.noInit.}, v2{.noInit.}: typeof(a.c0)
template a0: untyped = a.c0.c0
template a1: untyped = a.c0.c1
template a2: untyped = a.c1.c0
template a3: untyped = a.c1.c1
template a4: untyped = a.c2.c0
template a5: untyped = a.c2.c1
v0.square(a.c0)
v1.square(a.c1)
v2.square(a.c2)
# From here on, r aliasing with a is only for the first operation
# and only read/write the exact same coordinates
# 3v₀₀ - 2a₀
r.c0.c0.diff(v0.c0, a0)
r.c0.c0.double()
r.c0.c0 += v0.c0
# 3v₀₁ + 2a₁
r.c0.c1.sum(v0.c1, a1)
r.c0.c1.double()
r.c0.c1 += v0.c1
# 3v₁₀ - 2a₄
r.c2.c0.diff(v1.c0, a4)
r.c2.c0.double()
r.c2.c0 += v1.c0
# 3v₁₁ + 2b₅
r.c2.c1.sum(v1.c1, a5)
r.c2.c1.double()
r.c2.c1 += v1.c1
# Now B = 3 √i c² + 2 ̄b
# beware of mul by non residue: √i v₂ = ξv₂₁ + v₂₀√i
# 3 (√i c²)₀ + 2a₂
v2.c1 *= NonResidue
r.c1.c0.sum(v2.c1, a2)
r.c1.c0.double()
r.c1.c0 += v2.c1
# 3 (√i c²)₁ - 2a₃
r.c1.c1.diff(v2.c0, a3)
r.c1.c1.double()
r.c1.c1 += v2.c0
func cyclotomic_square_quad_over_cube[F](r: var QuadraticExt[F], a: QuadraticExt[F]) =
## Square `a` into `r`
## `a` MUST be in the cyclotomic subgroup
## consequently `a` MUST be unitary
# Mapping between towering schemes
# --------------------------------
#
# canonical <=> cubic over quadratic <=> quadratic over cubic
# c₀ <=> a₀ <=> b₀
# c₁ <=> a₂ <=> b₃
# c₂ <=> a₄ <=> b₁
# c₃ <=> a₁ <=> b₄
# c₄ <=> a₃ <=> b₂
# c₅ <=> a₅ <=> b₅
#
# Hence, this formula for a cubic extension field
# A = 3a² 2 ̄a
# B = 3 √i c² + 2 ̄b
# C = 3b² 2 ̄c
#
# becomes
# A = (b₀, b₄) = 3(b₀, b₄)² - 2(b₀,-b₄)
# B = (b₃, b₂) = 3 √i(b₁, b₅)² + 2(b₃, -b₂)
# C = (b₁, b₅) = 3(b₃, b₂)² - 2(b₁, -b₅)
#
# with
# v₀ = (b₀, b₄) = (a.c0.c0, a.c1.c1)
# v₁ = (b₃, b₂) = (a.c1.c0, a.c0.c2)
# v₂ = (b₁, b₅) = (a.c0.c1, a.c1.c2)
var v0{.noInit.}, v1{.noInit.}, v2{.noInit.}: QuadraticExt[typeof(r.c0.c0)]
template b0: untyped = a.c0.c0
template b1: untyped = a.c0.c1
template b2: untyped = a.c0.c2
template b3: untyped = a.c1.c0
template b4: untyped = a.c1.c1
template b5: untyped = a.c1.c2
v0.square_disjoint(b0, b4)
v1.square_disjoint(b3, b2)
v2.square_disjoint(b1, b5)
# From here on, r aliasing with a is only for the first operation
# and only read/write the exact same coordinates
# 3v₀₀ - 2b₀
r.c0.c0.diff(v0.c0, b0)
r.c0.c0.double()
r.c0.c0 += v0.c0
# 3v₁₀ - 2b₁
r.c0.c1.diff(v1.c0, b1)
r.c0.c1.double()
r.c0.c1 += v1.c0
# 3v₀₁ + 2b₄
r.c1.c1.sum(v0.c1, b4)
r.c1.c1.double()
r.c1.c1 += v0.c1
# 3v₁₁ + 2b₅
r.c1.c2.sum(v1.c1, b5)
r.c1.c2.double()
r.c1.c2 += v1.c1
# Now B = (b₃, b₂) = 3 √i(b₁, b₅)² + 2(b₃, -b₂)
# beware of mul by non residue: √i v₂ = ξv₂₁ + v₂₀√i
# 3 (√i (b₁, b₅)²)₀ + 2b₃
v2.c1 *= NonResidue
r.c1.c0.sum(v2.c1, b3)
r.c1.c0.double()
r.c1.c0 += v2.c1
# 3 (√i (b₁, b₅)²)₁ - 2b₃
r.c0.c2.diff(v2.c0, b2)
r.c0.c2.double()
r.c0.c2 += v2.c0
func cyclotomic_square*[FT](r: var FT, a: FT) {.inline, meter.} =
## Square `a` into `r`
## `a` MUST be in the cyclotomic subgroup
## consequently `a` MUST be unitary
#
# Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions
# Granger, Scott, 2009
# https://eprint.iacr.org/2009/565.pdf
when a is CubicExt:
r.cyclotomic_square_cube_over_quad(a)
else:
r.cyclotomic_square_quad_over_cube(a)
func cyclotomic_square*[FT](a: var FT) {.inline.} =
## Square `a` into `r`
## `a` MUST be in the cyclotomic subgroup
## consequently `a` MUST be unitary
#
# Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions
# Granger, Scott, 2009
# https://eprint.iacr.org/2009/565.pdf
a.cyclotomic_square(a)
func cycl_sqr_repeated*[FT](f: var FT, num: int) {.inline, meter.} =
## Repeated cyclotomic squarings
for _ in 0 ..< num:
f.cyclotomic_square()
func cycl_sqr_repeated*[FT](r: var FT, a: FT, num: int) {.inline, meter.} =
## Repeated cyclotomic squarings
r.cyclotomic_square(a)
for _ in 1 ..< num:
r.cyclotomic_square()
iterator unpack(scalarByte: byte): bool =
yield bool((scalarByte and 0b10000000) shr 7)
yield bool((scalarByte and 0b01000000) shr 6)
yield bool((scalarByte and 0b00100000) shr 5)
yield bool((scalarByte and 0b00010000) shr 4)
yield bool((scalarByte and 0b00001000) shr 3)
yield bool((scalarByte and 0b00000100) shr 2)
yield bool((scalarByte and 0b00000010) shr 1)
yield bool( scalarByte and 0b00000001)
func cyclotomic_exp*[FT](r: var FT, a: FT, exponent: BigInt, invert: bool) {.meter.} =
var eBytes: array[(exponent.bits+7) div 8, byte]
eBytes.marshal(exponent, bigEndian)
r.setOne()
for b in eBytes:
for bit in unpack(b):
r.cyclotomic_square()
if bit:
r *= a
if invert:
r.cyclotomic_inv()
func isInCyclotomicSubgroup*[C](a: Fp6[C]): SecretBool =
## Check if a ∈ Fpⁿ: a^Φₙ(p) = 1
## Φ₆(p) = p²-p+1
var t{.noInit.}, p{.noInit.}: Fp6[C]
t.frobenius_map(a, 2) # a^(p²)
t *= a # a^(p²+1)
p.frobenius_map(a) # a^(p)
return t == p and not a.isZero()
func isInCyclotomicSubgroup*[C](a: Fp12[C]): SecretBool =
## Check if a ∈ Fpⁿ: a^Φₙ(p) = 1
## Φ₁₂(p) = p⁴-p²+1
var t{.noInit.}, p2{.noInit.}: Fp12[C]
p2.frobenius_map(a, 2) # a^(p²)
t.frobenius_map(p2, 2) # a^(p⁴)
t *= a # a^(p⁴+1)
return t == p2 and not a.isZero()
# ############################################################
#
# Compressed representations
#
# ############################################################
#
# The special structure of cyclotomic subgroup allows compression that
# can lead to faster exponentiation:
#
# - Compression in Finite Fields and Torus-Based Cryptography
# Rubin and Silverberg, 2003
# https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.90.8087&rep=rep1&type=pdf
#
# - Squaring in Cyclotomic Subgroup
# Karabina, 2012
# https://www.ams.org/journals/mcom/2013-82-281/S0025-5718-2012-02625-1/S0025-5718-2012-02625-1.pdf
#
# Karabina's formula G₂₃₄₅ has the best squaring/decompression cost ratio.
# From a sextic tower Fpᵏᐟ⁶ -> Fpᵏᐟ³ -> Fpᵏ with quadratic-non-residue u and cubic non-residue v
# α = (a₀+a₁u) + (b₀+b₁u)v + (c₀+c₁u)v²
# Compressed α: C(α) = (b₀+b₁u)v + (c₀+c₁u)v² = (g₂+g₃u)v + (g₄+g₅u)v²
# C(α)² = (h₂+h₃u)v + (h₄+h₅u)v²
# with h₂ = 2(g₂ + 3ξg₄g₅)
# h₃ = 3((g₄+g₅)(g₄+ξg₅) - (ξ+1)g₄g₅) - 2g₃
# h₄ = 3((g₂+g₃)(g₂+ξg₃) - (ξ+1)g₂g₃) - 2g₄
# h₅ = 2(g₅ + 3(g₂+g₃)(g₂+ξg₃))
#
# For decompression we can recompute the missing coefficients
# if g₂ != 0
# g₁ = (g₅²ξ + 3g₄² - 2g₃)/4g₂ g₀ = (2g₁² + g₂g₅ - 3g₃g₄)ξ + 1
# if g₂ == 0
# g₁ = 2g₄g₅/g₃ g₀ = (2g₁² - 3g₃g₄)ξ + 1
type G2345*[F] = object
## Compressed representation of cyclotomic subgroup element of a sextic extension
## (0 + 0u) + (g₂+g₃u)v + (g₄+g₅u)v²
g2, g3, g4, g5: F
func cyclotomic_square_compressed*[F](g: var G2345[F]) =
## Karabina's compressed squaring
## for sextic extension fields
# C(α)² = (h₂+h₃u)v + (h₄+h₅u)v²
# with
# h₂ = 2(g₂ + 3ξg₄g₅)
# h₃ = 3((g₄+g₅)(g₄+ξg₅) - (ξ+1)g₄g₅) - 2g₃
# h₄ = 3((g₂+g₃)(g₂+ξg₃) - (ξ+1)g₂g₃) - 2g₄
# h₅ = 2(g₅ + 3(g₂+g₃)(g₂+ξg₃))
# (4 mul, theorem 3.2 p561)
#
# or
# h₂ = 2g₂ + 6ξg₄g₅
# h₃ = 3g₄² + 3g₅²ξ - 2g₃
# h₄ = 3g₂² + 3g₃²ξ - 2g₄
# h₅ = 2g₅ + 6g₂g₃
# (2 mul, 4 sqr, section 5.3 p567)
#
# or
# h₂ = 2g₂ + 3ξ((g₄+g₅)²-g₄²-g₅²)
# h₃ = 3(g₄² + g₅²ξ) - 2g₃
# h₄ = 3(g₂² + g₃²ξ) - 2g₄
# h₅ = 2g₅ + 3 ((g₂+g₃)²-g₂²-g₃²)
# (6 sqr)
#
# or with quadratic arithmetic
# (h₂+h₃u) = 3u(g₄+g₅u)² + 2(g₂-g₃u)
# (h₄+h₅u) = 3 (g₂+g₃u)² - 2(g₄-g₅u)
# (2x2mul or 2x3sqr, section 5.3 p567)
var g2g3 {.noInit.} = QuadraticExt[F](coords:[g.g2, g.g3])
var g4g5 {.noInit.} = QuadraticExt[F](coords:[g.g4, g.g5])
var h2h3 {.noInit.}, h4h5 {.noInit.}: QuadraticExt[F]
h2h3.square(g4g5)
h2h3 *= NonResidue
h2h3 *= 3
h4h5.square(g2g3)
h4h5 *= 3
g2g3.double()
g4g5.double()
g.g2.sum(h2h3.c0, g2g3.c0)
g.g3.diff(h2h3.c1, g2g3.c1)
g.g4.diff(h4h5.c0, g4g5.c0)
g.g5.sum(h4h5.c1, g4g5.c1)
func recover_g1*[F](g1_num, g1_den: var F, g: G2345[F]) =
## Compute g₁ from coordinates g₂g₃g₄g₅
## of a cyclotomic subgroup element of a sextic extension field
# if g₂ != 0
# g₁ = (g₅²ξ + 3g₄² - 2g₃)/4g₂
# if g₂ == 0
# g₁ = 2g₄g₅/g₃
#
# Theorem 3.1, this is well-defined for all
# g in Gϕₙ \ {1}
# if g₂=g₃=0 then g₄=g₅=0 as well
# and g₀ = 1
let g2NonZero = not g.g2.isZero()
var t{.noInit.}: F
g1_num = g.g4
t = g.g5
t.ccopy(g.g4, g2NonZero)
t *= g1_num # g₄² or g₄g₅
g1_num = t
g1_num.csub(g.g3, g2NonZero) # g₄²- g₃
g1_num.double() # 2g₄²-2g₃ or 2g₄g₅
g1_num.cadd(t, g2NonZero) # 3g₄²-2g₃ or 2g₄g₅
t.square(g.g5)
t *= NonResidue
g1_num.cadd(t, g2NonZero) # g₅²ξ + 3g₄² - 2g₃ or 2g₄g₅
t.prod(g.g2, 4)
g1_den = g.g3
g1_den.ccopy(t, g2NonZero) # 4g₂ or g₃
func batch_ratio_g1s*[N: static int, F](
dst: var array[N, F],
src: array[N, tuple[g1_num, g1_den: F]]) =
## Batch inversion of g₁
## returns g1_numᵢ/g1_denᵢ
## This requires that all g1_den != 0 or all g1_den == 0
## which is the case if this is used to implement
## exponentiation in cyclotomic subgroup.
# Algorithm: Montgomery's batch inversion
# - Speeding the Pollard and Elliptic Curve Methods of Factorization
# Section 10.3.1
# Peter L. Montgomery
# https://www.ams.org/journals/mcom/1987-48-177/S0025-5718-1987-0866113-7/S0025-5718-1987-0866113-7.pdf
# - Modern Computer Arithmetic
# Section 2.5.1 Several inversions at once
# Richard P. Brent and Paul Zimmermann
# https://members.loria.fr/PZimmermann/mca/mca-cup-0.5.9.pdf
dst[0] = src[0].g1_den
for i in 1 ..< N:
dst[i].prod(dst[i-1], src[i].g1_den)
var accInv{.noInit.}: F
accInv.inv(dst[N-1])
for i in countdown(N-1, 1):
# Compute inverse
dst[i].prod(accInv, dst[i-1])
# Apply it
dst[i] *= src[i].g1_num
# Next iteration
accInv *= src[i].g1_den
dst[0].prod(accInv, src[0].g1_num)
func recover_g0*[F](
g0: var F, g1: F,
g: G2345[F]) =
## Compute g₀ from coordinates g₁g₂g₃g₄g₅
## of a cyclotomic subgroup element of a sextic extension field
var t{.noInit.}: F
t.square(g1)
g0.prod(g.g3, g.g4)
t -= g0
t.double()
t -= g0
g0.prod(g.g2, g.g5)
t += g0
g0.prod(t, NonResidue)
t.setOne()
g0 += t
func fromFpk*[Fpkdiv6, Fpk](
g: var G2345[Fpkdiv6],
a: Fpk) =
## Convert from a sextic extension to the Karabina g₂₃₄₅
## representation.
# GT representations isomorphisms
# ===============================
#
# Given a sextic twist, we can express all elements in terms of z = SNR¹ᐟ⁶
#
# The canonical direct sextic representation uses coefficients
#
# c₀ + c₁ z + c₂ z² + c₃ z³ + c₄ z⁴ + c₅ z⁵
#
# with z = SNR¹ᐟ⁶
#
# The cubic over quadatric towering
# ---------------------------------
#
# (a₀ + a₁ u) + (a₂ + a₃u) v + (a₄ + a₅u) v²
#
# with u = (SNR)¹ᐟ² and v = z = u¹ᐟ³ = (SNR)¹ᐟ⁶
#
# The quadratic over cubic towering
# ---------------------------------
#
# (b₀ + b₁x + b₂x²) + (b₃ + b₄x + b₅x²)y
#
# with x = (SNR)¹ᐟ³ and y = z = x¹ᐟ² = (SNR)¹ᐟ⁶
#
# Mapping between towering schemes
# --------------------------------
#
# g₂₃₄₅ uses the cubic over quadratic representation hence:
#
# c₀ <=> a₀ <=> b₀ <=> g₀
# c₁ <=> a₂ <=> b₃ <=> g₂
# c₂ <=> a₄ <=> b₁ <=> g₄
# c₃ <=> a₁ <=> b₄ <=> g₁
# c₄ <=> a₃ <=> b₂ <=> g₃
# c₅ <=> a₅ <=> b₅ <=> g₅
#
# See also chapter 6.4
# - Multiplication and Squaring on Pairing-Friendly Fields
# Augusto Jun Devegili and Colm Ó hÉigeartaigh and Michael Scott and Ricardo Dahab, 2006
# https://eprint.iacr.org/2006/471
when a is CubicExt:
when a.c0 is QuadraticExt:
g.g2 = a.c1.c0
g.g3 = a.c1.c1
g.g4 = a.c2.c0
g.g5 = a.c2.c1
else:
{.error: "a must be a sextic extension field".}
elif a is QuadraticExt:
when a.c0 is CubicExt:
g.g2 = a.c1.c0
g.g3 = a.c0.c2
g.g4 = a.c0.c1
g.g5 = a.c1.c2
else:
{.error: "a must be a sextic extension field".}
else:
{.error: "𝔽pᵏᐟ⁶ -> 𝔽pᵏ towering (direct sextic) is not implemented.".}
func asFpk*[Fpkdiv6, Fpk](
a: var Fpk,
g0, g1: Fpkdiv6,
g: G2345[Fpkdiv6]) =
## Convert from a sextic extension to the Karabina g₂₃₄₅
## representation.
when a is CubicExt:
when a.c0 is QuadraticExt:
a.c0.c0 = g0
a.c0.c1 = g1
a.c1.c0 = g.g2
a.c1.c1 = g.g3
a.c2.c0 = g.g4
a.c2.c1 = g.g5
else:
{.error: "a must be a sextic extension field".}
elif a is QuadraticExt:
when a.c0 is CubicExt:
a.c0.c0 = g0
a.c0.c1 = g.g4
a.c0.c2 = g.g3
a.c1.c0 = g.g2
a.c1.c1 = g1
a.c1.c2 = g.g5
else:
{.error: "a must be a sextic extension field".}
else:
{.error: "𝔽pᵏᐟ⁶ -> 𝔽pᵏ towering (direct sextic) is not implemented.".}
func cyclotomic_exp_compressed*[N: static int, Fpk](
r: var Fpk, a: Fpk,
squarings: static array[N, int]) =
## Exponentiation on the cyclotomic subgroup
## via compressed repeated squarings
## Exponentiation is done least-signigicant bits first
## `squarings` represents the number of squarings
## to do before the next multiplication.
type Fpkdiv6 = typeof(a.c0.c0)
var gs {.noInit.}: array[N, G2345[Fpkdiv6]]
var g {.noInit.}: G2345[Fpkdiv6]
g.fromFpk(a)
# Compressed squarings
for i in 0 ..< N:
for j in 0 ..< squarings[i]:
g.cyclotomic_square_compressed()
gs[i] = g
# Batch decompress
var g1s_ratio {.noInit.}: array[N, tuple[g1_num, g1_den: Fpkdiv6]]
for i in 0 ..< N:
recover_g1(g1s_ratio[i].g1_num, g1s_ratio[i].g1_den, gs[i])
var g1s {.noInit.}: array[N, Fpkdiv6]
g1s.batch_ratio_g1s(g1s_ratio)
var g0s {.noInit.}: array[N, Fpkdiv6]
for i in 0 ..< N:
g0s[i].recover_g0(g1s[i], gs[i])
r.asFpk(g0s[0], g1s[0], gs[0])
for i in 1 ..< N:
var t {.noInit.}: Fpk
t.asFpk(g0s[i], g1s[i], gs[i])
r *= t
func cyclotomic_exp_compressed*[N: static int, Fpk](
r, accumSquarings: var Fpk, a: Fpk,
squarings: static array[N, int]) =
## Exponentiation on the cyclotomic subgroup
## via compressed repeated squarings
## Exponentiation is done least-signigicant bits first
## `squarings` represents the number of squarings
## to do before the next multiplication.
##
## `accumSquarings` stores the accumulated squarings so far
## iff N != 1
type Fpkdiv6 = typeof(a.c0.c0)
var gs {.noInit.}: array[N, G2345[Fpkdiv6]]
var g {.noInit.}: G2345[Fpkdiv6]
g.fromFpk(a)
# Compressed squarings
for i in 0 ..< N:
for j in 0 ..< squarings[i]:
g.cyclotomic_square_compressed()
gs[i] = g
# Batch decompress
var g1s_ratio {.noInit.}: array[N, tuple[g1_num, g1_den: Fpkdiv6]]
for i in 0 ..< N:
recover_g1(g1s_ratio[i].g1_num, g1s_ratio[i].g1_den, gs[i])
var g1s {.noInit.}: array[N, Fpkdiv6]
g1s.batch_ratio_g1s(g1s_ratio)
var g0s {.noInit.}: array[N, Fpkdiv6]
for i in 0 ..< N:
g0s[i].recover_g0(g1s[i], gs[i])
r.asFpk(g0s[0], g1s[0], gs[0])
for i in 1 ..< N:
var t {.noInit.}: Fpk
t.asFpk(g0s[i], g1s[i], gs[i])
r *= t
if i+1 == N:
accumSquarings = t