𝔽p12 extension - initial commit of squaring

This commit is contained in:
Mamy André-Ratsimbazafy 2020-04-09 01:23:10 +02:00
parent e47159e40d
commit 8c478df0c1
No known key found for this signature in database
GPG Key ID: 7B88AD1FE79492E1
3 changed files with 294 additions and 17 deletions

View File

@ -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()

View File

@ -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)

171
tests/test_fp12.nim Normal file
View File

@ -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)