diff --git a/constantine/tower_field_extensions/fp12_quad_fp6.nim b/constantine/tower_field_extensions/fp12_quad_fp6.nim new file mode 100644 index 0000000..4ca4856 --- /dev/null +++ b/constantine/tower_field_extensions/fp12_quad_fp6.nim @@ -0,0 +1,107 @@ +# 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. + +# ############################################################ +# +# Quadratic Extension field over extension field 𝔽p6 +# 𝔽p12 = 𝔽p6[√γ] +# with γ the cubic root of the non-residue of 𝔽p6 +# +# ############################################################ + +# This implements a quadratic extension field over +# 𝔽p12 = 𝔽p6[γ] +# with γ the cubic root of the non-residue of 𝔽p6 +# with element A of coordinates (a0, a1) represented +# by a0 + a1 γ +# +# The irreducible polynomial chosen is +# w² - γ +# with γ the cubic root of the non-residue of 𝔽p6 +# I.e. if 𝔽p6 irreducible polynomial is +# v³ - ξ with ξ = 1+𝑖 +# γ = v = ∛(1 + 𝑖) +# +# Consequently, for this file 𝔽p12 to be valid +# ∛(1 + 𝑖) MUST not be a square in 𝔽p6 + +import + ../arithmetic, + ../config/curves, + ./abelian_groups, + ./fp6_1_plus_i + +type + Fp12*[C: static Curve] = object + ## Element of the extension field + ## 𝔽p12 = 𝔽p6[γ] + ## + ## I.e. if 𝔽p6 irreducible polynomial is + ## v³ - ξ with ξ = 1+𝑖 + ## γ = v = ∛(1 + 𝑖) + ## + ## with coordinates (c0, c1) such as + ## c0 + c1 w + c0*, c1*: Fp6[C] + + Gamma = object + ## γ (Gamma) the quadratic non-residue of 𝔽p6 + ## γ = v with v the factor in for 𝔽p6 coordinate + ## i.e. a point in 𝔽p6 as coordinates a0 + a1 v + a2 v² + +func `*`(_: typedesc[Gamma], a: Fp6): Fp6 {.noInit, inline.} = + ## Multiply an element of 𝔽p6 by 𝔽p12 quadratic non-residue + ## Conveniently γ = v with v the factor in for 𝔽p6 coordinate + ## and v³ = ξ + ## (c0 + c1 v + c2 v²) v => ξ c2 + c0 v + c1 v² + + result.c0 = a.c2 * Xi + result.c1 = a.c0 + result.c2 = a.c1 + +template `*`(a: Fp6, _: typedesc[Gamma]): Fp6 = + Gamma * a + +func `*=`(a: var Fp6, _: typedesc[Gamma]) {.inline.} = + a = Gamma * a + +func square*(r: var Fp12, a: Fp12) = + ## Return a² in ``r`` + ## ``r`` is initialized/overwritten + # (c0, c1)² => (c0 + c1 w)² + # => c0² + 2 c0 c1 w + c1²w² + # => c0² + γ c1² + 2 c0 c1 w + # => (c0² + γ c1², 2 c0 c1) + # We have 2 squarings and 1 multiplication in 𝔽p6 + # which are significantly more costly: + # - 4 limbs like BN254: multiplication is 20x slower than addition/substraction + # - 6 limbs like BLS12-381: multiplication is 28x slower than addition/substraction + # + # We can save operations with one of the following expressions + # of c0² + γ c1² and noticing that c0c1 is already computed for the "y" coordinate + # + # Alternative 1: + # c0² + γ c1² <=> (c0 - c1)(c0 - γ c1) + γ c0c1 + c0c1 + # + # Alternative 2: + # c0² + γ c1² <=> (c0 + c1)(c0 + γ c1) - γ c0c1 - c0c1 + + # r0 <- (c0 - c1)(c0 - γ c1) + r.c0.diff(a.c0, a.c1) + r.c1.diff(a.c0, Gamma * a.c1) + r.c0.prod(r.c0, r.c1) + + # r1 <- c0 c1 + r.c1.prod(a.c0, a.c1) + + # r0 = (c0 - c1)(c0 - γ c1) + γ c0c1 + c0c1 + r.c0 += Gamma * r.c1 + r.c0 += r.c1 + + # r1 = 2 c0c1 + r.c1.double() diff --git a/constantine/tower_field_extensions/fp6_1_plus_i.nim b/constantine/tower_field_extensions/fp6_1_plus_i.nim index af5cc35..0c35e4c 100644 --- a/constantine/tower_field_extensions/fp6_1_plus_i.nim +++ b/constantine/tower_field_extensions/fp6_1_plus_i.nim @@ -8,23 +8,22 @@ # ############################################################ # -# Cubic Extension field over base field 𝔽p2 +# Cubic Extension field over extension field 𝔽p2 # 𝔽p6 = 𝔽p2[∛(1 + 𝑖)] # # ############################################################ -# This implements a quadratic extension field over 𝔽p2 = 𝔽p[𝑖] -# the base field 𝔽p: +# This implements a quadratic extension field over # 𝔽p6 = 𝔽p2[∛(1 + 𝑖)] -# with element A of coordinates (a0, a1) represented -# by a0 + a1 ξ + a2 ξ² +# with element A of coordinates (a0, a1, a2) represented +# by a0 + a1 v + a2 v² # # The irreducible polynomial chosen is -# x³ - ξ with ξ = 𝑖+1 +# v³ - ξ with ξ = 𝑖+1 # # -# Consequently, for this file Fp2 to be valid -# 𝑖+1 MUST not be a square in 𝔽p2 +# Consequently, for this file 𝔽p6 to be valid +# 𝑖+1 MUST not be a cube in 𝔽p2 import ../arithmetic, @@ -38,25 +37,25 @@ type ## 𝔽p6 = 𝔽p2[∛(1 + 𝑖)] ## ## with coordinates (c0, c1, c2) such as - ## c0 + c1 ξ + c2 ξ² + ## c0 + c1 v + c2 v² and v³ = ξ = 1+𝑖 ## ## This requires 1 + 𝑖 to not be a cube in 𝔽p2 c0*, c1*, c2*: Fp2[C] - Xi = object - ## ξ (Xi) the cubic non-residue + Xi* = object + ## ξ (Xi) the cubic non-residue of 𝔽p2 -func `*`(_: typedesc[Xi], a: Fp2): Fp2 {.inline.}= - ## Multiply an element of 𝔽p2 by 𝔽p6 cubic non-residue 1 + 𝑖 +func `*`*(_: typedesc[Xi], a: Fp2): Fp2 {.inline.}= + ## Multiply an element of 𝔽p2 by 𝔽p6 cubic non-residue ξ = 1 + 𝑖 ## (c0 + c1 𝑖) (1 + 𝑖) => c0 + (c0 + c1)𝑖 + c1 𝑖² - ## => c0 - c1 + (c0 + c1) 𝑖 + ## => c0 - c1 + (c0 + c1) 𝑖 result.c0.diff(a.c0, a.c1) result.c1.sum(a.c0, a.c1) -template `*`(a: Fp2, _: typedesc[Xi]): Fp2 = +template `*`*(a: Fp2, _: typedesc[Xi]): Fp2 = Xi * a -func `*=`(a: var Fp2, _: typedesc[Xi]) {.inline.}= +func `*=`*(a: var Fp2, _: typedesc[Xi]) {.inline.}= ## Inplace multiply an element of 𝔽p2 by 𝔽p6 cubic non-residue 1 + 𝑖 let t = a.c0 a.c0 -= a.c1 @@ -171,7 +170,7 @@ func inv*[C](r: var Fp6[C], a: Fp6[C]) = v3.inv(v3) - # (a0 + a1 ξ + a2 ξ²)^-1 = (A + B ξ + C ξ²) / F + # (a0 + a1 v + a2 v²)^-1 = (A + B v + C v²) / F r.c0 *= v3 r.c1.prod(v1, v3) r.c2.prod(v2, v3) diff --git a/tests/test_fp12.nim b/tests/test_fp12.nim new file mode 100644 index 0000000..dad6aa6 --- /dev/null +++ b/tests/test_fp12.nim @@ -0,0 +1,171 @@ +# 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 + # Standard library + unittest, times, random, + # Internals + ../constantine/tower_field_extensions/[abelian_groups, fp12_quad_fp6], + ../constantine/config/[common, curves], + ../constantine/arithmetic, + # Test utilities + ../helpers/prng + +const Iters = 128 + +var rng: RngState +let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 +rng.seed(seed) +echo "test_fp12 xoshiro512** seed: ", seed + +# Import: wrap in field element tests in small procedures +# otherwise they will become globals, +# and will create binary size issues. +# Also due to Nim stack scanning, +# having too many elements on the stack (a couple kB) +# will significantly slow down testing (100x is possible) + +suite "𝔽p12 = 𝔽p6[√∛(1+𝑖)]": + test "Squaring 1 returns 1": + template test(C: static Curve) = + block: + proc testInstance() = + let One = block: + var O{.noInit.}: Fp12[C] + O.setOne() + O + block: + var r{.noinit.}: Fp12[C] + r.square(One) + check: bool(r == One) + # block: + # var r{.noinit.}: Fp12[C] + # r.prod(One, One) + # check: bool(r == One) + + testInstance() + + test(BN254) + test(BLS12_377) + test(BLS12_381) + test(BN446) + test(FKM12_447) + test(BLS12_461) + test(BN462) + + test "Squaring 2 returns 4": + template test(C: static Curve) = + block: + proc testInstance() = + let One = block: + var O{.noInit.}: Fp12[C] + O.setOne() + O + + var Two: Fp12[C] + Two.double(One) + + var Four: Fp12[C] + Four.double(Two) + + block: + var r: Fp12[C] + r.square(Two) + + check: bool(r == Four) + # block: + # var r: Fp12[C] + # r.prod(Two, Two) + + # check: bool(r == Four) + + testInstance() + + # test(BN254) + # test(BLS12_377) + # test(BLS12_381) + # test(BN446) + # test(FKM12_447) + # test(BLS12_461) + # test(BN462) + + test "Squaring 3 returns 9": + template test(C: static Curve) = + block: + proc testInstance() = + let One = block: + var O{.noInit.}: Fp12[C] + O.setOne() + O + + var Three: Fp12[C] + for _ in 0 ..< 3: + Three += One + + var Nine: Fp12[C] + for _ in 0 ..< 9: + Nine += One + + block: + var u: Fp12[C] + u.square(Three) + + check: bool(u == Nine) + # block: + # var u: Fp12[C] + # u.prod(Three, Three) + + # check: bool(u == Nine) + + testInstance() + + # test(BN254) + # test(BLS12_377) + # test(BLS12_381) + # test(BN446) + # test(FKM12_447) + # test(BLS12_461) + # test(BN462) + + test "Squaring -3 returns 9": + template test(C: static Curve) = + block: + proc testInstance() = + let One = block: + var O{.noInit.}: Fp12[C] + O.setOne() + O + + var MinusThree: Fp12[C] + for _ in 0 ..< 3: + MinusThree -= One + + var Nine: Fp12[C] + for _ in 0 ..< 9: + Nine += One + + block: + var u: Fp12[C] + u.square(MinusThree) + + check: bool(u == Nine) + # block: + # var u: Fp12[C] + # u.prod(MinusThree, MinusThree) + + # check: bool(u == Nine) + + testInstance() + + # test(BN254) + # test(BLS12_377) + # test(BLS12_381) + # test(BN446) + # test(FKM12_447) + # test(BLS12_461) + # test(BN462)