Skip to content

Commit

Permalink
feat(𝔾ₜ exponentiation): Add square-multiply and signed recoding and …
Browse files Browse the repository at this point in the history
…wNAF 𝔾ₜ exponentiation
  • Loading branch information
mratsim committed Jul 14, 2024
1 parent 52a8fc4 commit b73df48
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 29 deletions.
4 changes: 2 additions & 2 deletions README-PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ The full list of benchmarks is available in the [`benchmarks`](./benchmarks) fol

As mentioned in the [Compiler caveats](#compiler-caveats) section, GCC is up to 2x slower than Clang due to mishandling of carries and register usage.

#### Ethereum BLS signatures (over BLS12-381 G2)
#### Ethereum BLS signatures (over BLS12-381 𝔾₂)

![Bench Ethereum BLS signature](./media/ethereum_bls_signatures.png)

Expand Down Expand Up @@ -192,4 +192,4 @@ Constantine does not use heap allocation.
At the moment Constantine is optimized for 32-bit and 64-bit CPUs.

When performance and code size conflicts, a careful and informed default is chosen.
In the future, a compile-time flag that goes beyond the compiler `-Os` might be provided.
In the future, a compile-time flag that goes beyond the compiler `-Os` might be provided.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ For all elliptic curves, the following arithmetic is supported
- on Fr (i.e. modulo the 255-bit curve order)
- on Fp (i.e. modulo the 381-bit prime modulus)
- elliptic curve arithmetic:
- on elliptic curve over Fp (EC G1) with affine, jacobian and homogenous projective coordinates
- on elliptic curve over Fp2 (EC G2) with affine, jacobian and homogenous projective coordinates
- on elliptic curve over Fp (EC 𝔾₁) with affine, jacobian and homogenous projective coordinates
- on elliptic curve over Fp2 (EC 𝔾₂) with affine, jacobian and homogenous projective coordinates
- including scalar multiplication, multi-scalar-multiplication (MSM) and parallel MSM

_All operations are constant-time unless explicitly mentioned_ vartime.
Expand Down Expand Up @@ -222,7 +222,7 @@ and modify Constantine's [`build.rs`](https://github.com/mratsim/constantine/blo
```
> [!IMPORTANT]
> Constantine uses a separate modfile for tests.<br />It has no dependencies (key to avoid supply chain attacks) except for testing.
### From C

1. Install a C compiler, `clang` is recommended, for example:
Expand Down
20 changes: 11 additions & 9 deletions constantine.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# ----------------------------------------------------------
("tests/math_elliptic_curves/t_ec_conversion.nim", false),

# Elliptic curve arithmetic G1
# Elliptic curve arithmetic 𝔾₁
# ----------------------------------------------------------
("tests/math_elliptic_curves/t_ec_shortw_prj_g1_add_double.nim", false),
# ("tests/math_elliptic_curves/t_ec_shortw_prj_g1_mul_sanity.nim", false),
Expand Down Expand Up @@ -458,7 +458,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/math_elliptic_curves/t_ec_twedwards_mul_endomorphism_bandersnatch", false),


# Elliptic curve arithmetic G2
# Elliptic curve arithmetic 𝔾₂
# ----------------------------------------------------------
# ("tests/math_elliptic_curves/t_ec_shortw_prj_g2_add_double_bn254_snarks.nim", false),
# ("tests/math_elliptic_curves/t_ec_shortw_prj_g2_mul_sanity_bn254_snarks.nim", false),
Expand Down Expand Up @@ -551,7 +551,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# ("tests/math_pairings/t_pairing_bls12_381_gt_subgroup.nim", false),
# ("tests/math_pairings/t_pairing_bw6_761_gt_subgroup.nim", false),

# Pairing
# Pairing &
# ----------------------------------------------------------
# ("tests/math_pairings/t_pairing_bls12_377_line_functions.nim", false),
# ("tests/math_pairings/t_pairing_bls12_381_line_functions.nim", false),
Expand All @@ -562,6 +562,8 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# ("tests/math_pairings/t_pairing_bls12_377_optate.nim", false),
# ("tests/math_pairings/t_pairing_bls12_381_optate.nim", false),

("tests/math_pairings/t_pairing_bn254_snarks_gt_exp.nim", false),

# Multi-Pairing
# ----------------------------------------------------------
("tests/math_pairings/t_pairing_bn254_nogami_multi.nim", false),
Expand Down Expand Up @@ -977,25 +979,25 @@ task bench_fp6, "Run benchmark 𝔽p6 with your CC compiler":
task bench_fp12, "Run benchmark 𝔽p12 with your CC compiler":
runBench("bench_fp12")

# Elliptic curve G1
# Elliptic curve 𝔾₁
# ------------------------------------------

task bench_ec_g1, "Run benchmark on Elliptic Curve group 𝔾1 - CC compiler":
runBench("bench_ec_g1")

# Elliptic curve G1 - batch operations
# Elliptic curve 𝔾₁ - batch operations
# ------------------------------------------

task bench_ec_g1_batch, "Run benchmark on Elliptic Curve group 𝔾1 (batch ops) - CC compiler":
runBench("bench_ec_g1_batch")

# Elliptic curve G1 - scalar multiplication
# Elliptic curve 𝔾₁ - scalar multiplication
# ------------------------------------------

task bench_ec_g1_scalar_mul, "Run benchmark on Elliptic Curve group 𝔾1 (Scalar Multiplication) - CC compiler":
runBench("bench_ec_g1_scalar_mul")

# Elliptic curve G1 - Multi-scalar-mul
# Elliptic curve 𝔾₁ - Multi-scalar-mul
# ------------------------------------------

task bench_ec_msm_pasta, "Run benchmark: Multi-Scalar-Mul for Pasta curves - CC compiler":
Expand All @@ -1014,13 +1016,13 @@ task bench_ec_msm_bandersnatch, "Run benchmark: Multi-Scalar-Mul for Bandersnatc
runBench("bench_ec_msm_bandersnatch")


# Elliptic curve G2
# Elliptic curve 𝔾₂
# ------------------------------------------

task bench_ec_g2, "Run benchmark on Elliptic Curve group 𝔾2 - CC compiler":
runBench("bench_ec_g2")

# Elliptic curve G2 - scalar multiplication
# Elliptic curve 𝔾₂ - scalar multiplication
# ------------------------------------------

task bench_ec_g2_scalar_mul, "Run benchmark on Elliptic Curve group 𝔾2 (Multi-Scalar-Mul) - CC compiler":
Expand Down
2 changes: 1 addition & 1 deletion constantine/math/elliptic/ec_scalar_mul_vartime.nim
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func initNAF[precompSize, NafMax: static int, EC, ECaff](
P.fromAffine(tab[digit shr 1])
return true
elif digit < 0:
P.fromAffine(tab[digit shr 1])
P.fromAffine(tab[-digit shr 1])
P.neg()
return true
else:
Expand Down
3 changes: 0 additions & 3 deletions constantine/math/extension_fields/towers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,6 @@ func has2extraBits*(E: type ExtensionField): bool =
## We construct extensions only on Fp (and not Fr)
getSpareBits(Fp[E.F.Name]) >= 2

template A(E: type ExtensionField2x): Algebra =
E.F.Name

template c0*(a: ExtensionField2x): auto =
a.coords[0]
template c1*(a: ExtensionField2x): auto =
Expand Down
233 changes: 233 additions & 0 deletions constantine/math/pairings/gt_exponentiations_vartime.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# 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
# Internals
constantine/math/elliptic/ec_endomorphism_accel,
constantine/math/arithmetic,
constantine/math/extension_fields,
constantine/math/io/io_bigints,
constantine/platforms/abstractions,
constantine/math_arbitrary_precision/arithmetic/limbs_views,
constantine/named/zoo_endomorphisms,
constantine/named/algebras,
./cyclotomic_subgroups

{.push raises: [].} # No exceptions allowed in core cryptographic operations
{.push checks: off.} # No defects due to array bound checking or signed integer overflow allowed

iterator unpackBE(scalarByte: byte): bool =
for i in countdown(7, 0):
yield bool((scalarByte shr i) and 1)

func gtExp_sqrmul_vartime*[Gt: ExtensionField](r: var Gt, a: Gt, scalar: BigInt) {.tags:[VarTime], meter.} =
## **Variable-time** Exponentiation in Gt
##
## r <- aᵏ
##
## This uses the square-and-multiply algorithm
## This MUST NOT be used with secret data.
##
## This is highly VULNERABLE to timing attacks and power analysis attacks.
var scalarCanonical: array[scalar.bits.ceilDiv_vartime(8), byte]
scalarCanonical.marshal(scalar, bigEndian)

let a {.noInit.} = a # Avoid aliasing issues

r.setOne()
var isNeutral = true

for scalarByte in scalarCanonical:
for bit in unpackBE(scalarByte):
if not isNeutral:
r.square()
if bit:
if isNeutral:
r = a
isNeutral = false
else:
r *= a

func gtExp_addchain_4bit_vartime[Gt: ExtensionField](r: var Gt, a: Gt, scalar: BigInt) {.tags:[VarTime], meter.} =
## **Variable-time** Exponentiation in Gt
## This can only handle for small scalars up to 2⁴ = 16 excluded
let s = uint scalar.limbs[0]

case s
of 0:
r.setNeutral()
of 1:
discard
of 2:
r.square(a)
of 3:
var t {.noInit.}: Gt
t.square(a)
r.prod(a, t)
of 4:
r.square(a)
r.square()
of 5:
var t {.noInit.}: Gt
t.square(a)
t.square()
r.prod(a, t)
of 6:
var t {.noInit.}: Gt
t.square(a)
r.prod(a, t)
r.square()
of 7:
var t {.noInit.}: Gt
t.square(a)
t.square()
t.square()
r.cyclotomic_inv(a)
r *= t
of 8:
r.square(a)
r.square()
r.square()
of 9:
var t {.noInit.}: Gt
t.square(a)
t.square()
t.square()
r.prod(a, t)
of 10:
var t {.noInit.}: Gt
t.square(a)
t.square()
r.prod(a, t)
r.square()
of 11:
var t1 {.noInit.}, t2 {.noInit.}: Gt
t1.square(a) # [2]P
t2.square(t1)
t2.square() # [8]P
t1 *= t2
r.prod(a, t1)
of 12:
var t1 {.noInit.}, t2 {.noInit.}: Gt
t1.square(a)
t1.square() # [4]P
t2.square(t1) # [8]P
r.prod(t1, t2)
of 13:
var t1 {.noInit.}, t2 {.noInit.}: Gt
t1.square(a)
t1.square() # [4]P
t2.square(t1) # [8]P
t1 *= t2
r.prod(a, t1)
of 14:
var t {.noInit.}: Gt
t.square(a)
t.square()
t.square()
r.cyclotomic_inv(a)
t *= r # [7]P
r.square(t)
of 15:
var t {.noInit.}: Gt
t.square(a)
t.square()
t.square()
t.square()
r.cyclotomic_inv(a)
r *= t
else:
unreachable()

func gtExp_minHammingWeight_vartime*[Gt: ExtensionField](r: var Gt, a: Gt, scalar: BigInt) {.tags:[VarTime].} =
## **Variable-time** Exponentiation in Gt
##
## r <- aᵏ
##
## This uses an online recoding with minimum Hamming Weight
## (which is not NAF, NAF is least-significant bit to most)
## This MUST NOT be used with secret data.
##
## This is highly VULNERABLE to timing attacks and power analysis attacks
let a {.noInit.} = a # Avoid aliasing issues
var na {.noInit.}: Gt
na.cyclotomic_inv(a)

r.setOne()
for bit in recoding_l2r_signed_vartime(scalar):
r.square()
if bit == 1:
r *= a
elif bit == -1:
r *= na

func initNAF[precompSize, NafMax: static int, Gt: ExtensionField](
acc: var Gt,
tab: array[precompSize, Gt],
naf: array[NafMax, int8], nafLen: int,
nafIteratorIdx: int): bool {.inline.} =

let digit = naf[nafLen-1-nafIteratorIdx]
if digit > 0:
acc = tab[digit shr 1]
return true
elif digit < 0:
acc.cyclotomic_inv(tab[digit shr 1])
return true
else:
acc.setOne()
return false

func accumNAF[precompSize, NafMax: static int, Gt: ExtensionField](
acc: var Gt,
tab: array[precompSize, Gt],
naf: array[NafMax, int8], nafLen: int,
nafIteratorIdx: int) {.inline.} =

let digit = naf[nafLen-1-nafIteratorIdx]
if digit > 0:
acc *= tab[digit shr 1]
elif digit < 0:
var neg {.noInit.}: Gt
neg.cyclotomic_inv(tab[-digit shr 1])
acc *= neg

func gtExp_minHammingWeight_windowed_vartime*[Gt: ExtensionField](
r: var Gt, a: Gt, scalar: BigInt, window: static int) {.tags:[VarTime], meter.} =
## **Variable-time** Exponentiation in Gt
##
## r <- aᵏ
##
## This uses windowed-NAF (wNAF)
## This MUST NOT be used with secret data.
##
## This is highly VULNERABLE to timing attacks and power analysis attacks

# Signed digits divides precomputation table size by 2
# Odd-only divides precomputation table size by another 2

const precompSize = 1 shl (window - 2)
static: doAssert window < 8, "Window is too large and precomputation would use " & $(precompSize * sizeof(Gt)) & " stack space."

var tab {.noinit.}: array[precompSize, Gt]
var a2{.noInit.}: Gt
tab[0] = a
a2.square(a)
for i in 1 ..< tab.len:
tab[i].prod(tab[i-1], a2)

var naf {.noInit.}: array[BigInt.bits+1, int8]
let nafLen = naf.recode_r2l_signed_window_vartime(scalar, window)

var isInit = false
for i in 0 ..< nafLen:
if isInit:
r.square()
r.accumNAF(tab, naf, nafLen, i)
else:
isInit = r.initNAF(tab, naf, nafLen, i)
5 changes: 5 additions & 0 deletions constantine/named/zoo_endomorphisms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import
std/macros,
constantine/platforms/abstractions,
constantine/math/extension_fields,
constantine/math/isogenies/frobenius,
constantine/math/elliptic/[
Expand Down Expand Up @@ -103,6 +104,10 @@ func computeEndomorphisms*[EC; M: static int](endos: var array[M-1, EC], P: EC)
else:
{.error: "Unconfigured".}

func computeEndomorphisms*[Gt: ExtensionField; M: static int](endos: var array[M-1, Gt], a: Gt) =
staticFor i, 0, M-1:
endos[i].frobenius_map(a, i+1)

func hasEndomorphismAcceleration*(Name: static Algebra): bool {.compileTime.} =
Name in {
Bandersnatch,
Expand Down
Loading

0 comments on commit b73df48

Please sign in to comment.