From e9a1ef91fb8af4f4a0367d30f0e91d02a9f9b868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 14 Feb 2021 17:59:52 +0100 Subject: [PATCH] [Research] KZG polynomial commit and verify --- constantine/pairing/pairing_bls12_381.nim | 338 ------------------ research/kzg_poly_commit/fft_fr.nim | 2 +- research/kzg_poly_commit/fft_g1.nim | 2 +- .../kzg_poly_commit/kzg_single_proofs.nim | 82 +++++ research/kzg_poly_commit/polynomials.nim | 18 +- 5 files changed, 95 insertions(+), 347 deletions(-) delete mode 100644 constantine/pairing/pairing_bls12_381.nim create mode 100644 research/kzg_poly_commit/kzg_single_proofs.nim diff --git a/constantine/pairing/pairing_bls12_381.nim b/constantine/pairing/pairing_bls12_381.nim deleted file mode 100644 index 96476cf..0000000 --- a/constantine/pairing/pairing_bls12_381.nim +++ /dev/null @@ -1,338 +0,0 @@ -# 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 - ../config/[common, curves, type_ff], - ../towers, - ../elliptic/[ - ec_shortweierstrass_affine, - ec_shortweierstrass_projective - ], - ../curves/zoo_pairings, - ./lines_projective, ./mul_fp12_by_lines, - ./miller_loops - -# ############################################################ -# -# Optimal ATE pairing for -# BLS12-381 -# -# ############################################################ -# -# - Software Implementation, Algorithm 11.2 & 11.3 -# Aranha, Dominguez Perez, A. Mrabet, Schwabe, -# Guide to Pairing-Based Cryptography, 2015 -# -# - Physical Attacks, -# N. El Mrabet, Goubin, Guilley, Fournier, Jauvart, Moreau, Rauzy, Rondepierre, -# Guide to Pairing-Based Cryptography, 2015 -# -# - Pairing Implementation Revisited -# Mike Scott, 2019 -# https://eprint.iacr.org/2019/077.pdf -# -# Fault attacks: -# To limite exposure to some fault attacks (flipping bits with a laser on embedded): -# - changing the number of Miller loop iterations -# - flipping the bits in the Miller loop -# we hardcode unrolled addition chains. -# This should also contribute to performance. -# -# Multi-pairing discussion: -# Aranha & Scott proposes 2 different approaches for multi-pairing. -# -# ----- -# Scott -# -# Algorithm 2: Calculate and store line functions for BLS12 curve -# Input: Q ∈ G2, P ∈ G1 , curve parameter u -# Output: An array g of blog2(u)c line functions ∈ Fp12 -# 1 T ← Q -# 2 for i ← ceil(log2(u)) − 1 to 0 do -# 3 g[i] ← lT,T(P), T ← 2T -# 4 if ui = 1 then -# 5 g[i] ← g[i].lT,Q(P), T ← T + Q -# 6 return g -# -# And to accumulate lines from a new (P, Q) tuple of points -# -# Algorithm 4: Accumulate another set of line functions into g -# Input: The array g, Qj ∈ G2 , Pj ∈ G1 , curve parameter u -# Output: Updated array g of ceil(log2(u)) line functions ∈ Fp12 -# 1 T ← Qj -# 2 for i ← blog2 (u)c − 1 to 0 do -# 3 t ← lT,T (Pj), T ← 2T -# 4 if ui = 1 then -# 5 t ← t.lT,Qj (Pj), T ← T + Qj -# 6 g[i] ← g[i].t -# 7 return g -# -# ------ -# Aranha -# -# Algorithm 11.2 Explicit multipairing version of Algorithm 11.1. -# (we extract the Miller Loop part only) -# Input : P1 , P2 , . . . Pn ∈ G1 , -# Q1 , Q2, . . . Qn ∈ G2 -# Output: (we focus on the Miller Loop) -# -# Write l in binary form, l = sum(0 ..< m-1) -# f ← 1, l ← abs(AteParam) -# for j ← 1 to n do -# Tj ← Qj -# end -# -# for i = m-2 down to 0 do -# f ← f² -# for j ← 1 to n do -# f ← f gTj,Tj(Pj), Tj ← [2]Tj -# if li = 1 then -# f ← f gTj,Qj(Pj), Tj ← Tj + Qj -# end -# end -# end -# -# ----- -# Assuming we have N tuples (Pj, Qj) of points j in 0 ..< N -# and I operations to do in our Miller loop: -# - I = HammingWeight(AteParam) + Bitwidth(AteParam) -# - HammingWeight(AteParam) corresponds to line additions -# - Bitwidth(AteParam) corresponds to line doublings -# -# Scott approach is to have: -# - I Fp12 accumulators `g` -# - 1 G2 accumulator `T` -# and then accumulating each (Pj, Qj) into their corresponding `g` accumulator. -# -# Aranha approach is to have: -# - 1 Fp12 accumulator `f` -# - N G2 accumulators `T` -# and accumulate N points per I. -# -# Scott approach is fully "online"/"streaming", -# while Aranha's saves space. -# For BLS12_381, -# I = 68 hence we would need 68*12*48 = 39168 bytes (381-bit needs 48 bytes) -# G2 has size 3*2*48 = 288 bytes (3 proj coordinates on Fp2) -# and we choose N (which can be 1 for single pairing or reverting to Scott approach). -# -# In actual use, "streaming pairings" are not used, pairings to compute are receive -# by batch, for example for blockchain you receive a batch of N blocks to verify from one peer. -# Furthermore, 39kB would be over L1 cache size and incurs cache misses. -# Additionally Aranha approach would make it easier to batch inversions -# using Montgomery's simultaneous inversion technique. -# Lastly, while a higher level API will need to store N (Pj, Qj) pairs for multi-pairings -# for Aranha approach, it can decide how big N is depending on hardware and/or protocol. -# -# Regarding optimizations, as the Fp12 accumulator is dense -# and lines are sparse (xyz000 or xy000z) Scott mentions the following costs: -# - Dense-sparse is 13m -# - sparse-sparse is 6m -# - Dense-(somewhat sparse) is 17m -# Hence when accumulating lines from multiple points: -# - 2x Dense-sparse is 26m -# - sparse-sparse then Dense-(somewhat sparse) is 23m -# a 11.5% speedup -# -# We can use Aranha approach but process lines function 2-by-2 merging them -# before merging them to the dense Fp12 accumulator - -# Miller Loop -# ------------------------------------------------------------------------------------------------------- - -{.push raises: [].} - -import - strutils, - ../io/io_towers - -func miller_first_iter[N: static int]( - f: var Fp12[BLS12_381], - Ts: var array[N, ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist]], - Qs: array[N, ECP_ShortW_Aff[Fp2[BLS12_381], OnTwist]], - Ps: array[N, ECP_ShortW_Aff[Fp[BLS12_381], NotOnTwist]] - ) = - ## Start a Miller Loop - ## This means - ## - 1 doubling - ## - 1 add - ## - ## f is overwritten - ## Ts are overwritten by Qs - static: - doAssert N >= 1 - doAssert f.c0 is Fp4 - - {.push checks: off.} # No OverflowError or IndexError allowed - var line {.noInit.}: Line[Fp2[BLS12_381]] - - # First step: T <- Q, f = 1 (mod p¹²), f *= line - # ---------------------------------------------- - for i in 0 ..< N: - Ts[i].projectiveFromAffine(Qs[i]) - - line.line_double(Ts[0], Ps[0]) - - # f *= line <=> f = line for the first iteration - # With Fp2 -> Fp4 -> Fp12 towering and a M-Twist - # The line corresponds to a sparse xy000z Fp12 - f.c0.c0 = line.x - f.c0.c1 = line.y - f.c1.c0.setZero() - f.c1.c1.setZero() - f.c2.c0.setZero() - f.c2.c1 = line.z - - when N >= 2: - line.line_double(Ts[1], Ps[1]) - f.mul_sparse_by_line_xy000z(line) # TODO: sparse-sparse mul - - # Sparse merge 2 by 2, starting from 2 - for i in countup(2, N-1, 2): - # var f2 {.noInit.}: Fp12[BLS12_381] # TODO: sparse-sparse mul - var line2 {.noInit.}: Line[Fp2[BLS12_381]] - - line.line_double(Ts[i], Ps[i]) - line2.line_double(Ts[i+1], Ps[i+1]) - - # f2.mul_sparse_sparse(line, line2) - # f.mul_somewhat_sparse(f2) - f.mul_sparse_by_line_xy000z(line) - f.mul_sparse_by_line_xy000z(line2) - - when N and 1 == 1: # N >= 2 and N is odd, there is a leftover - line.line_double(Ts[N-1], Ps[N-1]) - f.mul_sparse_by_line_xy000z(line) - - # 2nd step: Line addition as MSB is always 1 - # ---------------------------------------------- - when N >= 2: # f is dense, there are already many lines accumulated - # Sparse merge 2 by 2, starting from 0 - for i in countup(0, N-1, 2): - # var f2 {.noInit.}: Fp12[BLS12_381] # TODO: sparse-sparse mul - var line2 {.noInit.}: Line[Fp2[BLS12_381]] - - line.line_add(Ts[i], Qs[i], Ps[i]) - line2.line_add(Ts[i+1], Qs[i+1], Ps[i+1]) - - # f2.mul_sparse_sparse(line, line2) - # f.mul_somewhat_sparse(f2) - f.mul_sparse_by_line_xy000z(line) - f.mul_sparse_by_line_xy000z(line2) - - when N and 1 == 1: # N >= 2 and N is odd, there is a leftover - line.line_add(Ts[N-1], Qs[N-1], Ps[N-1]) - f.mul_sparse_by_line_xy000z(line) - - else: # N = 1, f is sparse - line.line_add(Ts[0], Qs[0], Ps[0]) - # f.mul_sparse_sparse(line) - f.mul_sparse_by_line_xy000z(line) - - {.pop.} # No OverflowError or IndexError allowed - -func miller_accum_doublings[N: static int]( - f: var Fp12[BLS12_381], - Ts: var array[N, ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist]], - Ps: array[N, ECP_ShortW_Aff[Fp[BLS12_381], NotOnTwist]], - numDoublings: int - ) = - ## Accumulate `numDoublings` Miller loop doubling steps into `f` - static: doAssert N >= 1 - {.push checks: off.} # No OverflowError or IndexError allowed - - var line {.noInit.}: Line[Fp2[BLS12_381]] - - for _ in 0 ..< numDoublings: - f.square() - when N >= 2: - for i in countup(0, N-1, 2): - # var f2 {.noInit.}: Fp12[BLS12_381] # TODO: sparse-sparse mul - var line2 {.noInit.}: Line[Fp2[BLS12_381]] - - line.line_double(Ts[i], Ps[i]) - line2.line_double(Ts[i+1], Ps[i+1]) - - # f2.mul_sparse_sparse(line, line2) - # f.mul_somewhat_sparse(f2) - f.mul_sparse_by_line_xy000z(line) - f.mul_sparse_by_line_xy000z(line2) - - when N and 1 == 1: # N >= 2 and N is odd, there is a leftover - line.line_double(Ts[N-1], Ps[N-1]) - f.mul_sparse_by_line_xy000z(line) - else: - line.line_double(Ts[0], Ps[0]) - f.mul_sparse_by_line_xy000z(line) - - {.pop.} # No OverflowError or IndexError allowed - -func miller_accum_addition[N: static int]( - f: var Fp12[BLS12_381], - Ts: var array[N, ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist]], - Qs: array[N, ECP_ShortW_Aff[Fp2[BLS12_381], OnTwist]], - Ps: array[N, ECP_ShortW_Aff[Fp[BLS12_381], NotOnTwist]] - ) = - ## Accumulate a Miller loop addition step into `f` - static: doAssert N >= 1 - {.push checks: off.} # No OverflowError or IndexError allowed - - var line {.noInit.}: Line[Fp2[BLS12_381]] - - when N >= 2: - # Sparse merge 2 by 2, starting from 0 - for i in countup(0, N-1, 2): - # var f2 {.noInit.}: Fp12[BLS12_381] # TODO: sparse-sparse mul - var line2 {.noInit.}: Line[Fp2[BLS12_381]] - - line.line_add(Ts[i], Qs[i], Ps[i]) - line2.line_add(Ts[i+1], Qs[i+1], Ps[i+1]) - - # f2.mul_sparse_sparse(line, line2) - # f.mul_somewhat_sparse(f2) - f.mul_sparse_by_line_xy000z(line) - f.mul_sparse_by_line_xy000z(line2) - - when N and 1 == 1: # N >= 2 and N is odd, there is a leftover - line.line_add(Ts[N-1], Qs[N-1], Ps[N-1]) - f.mul_sparse_by_line_xy000z(line) - - else: - line.line_add(Ts[0], Qs[0], Ps[0]) - f.mul_sparse_by_line_xy000z(line) - - {.pop.} # No OverflowError or IndexError allowed - -func millerLoop_opt_BLS12_381*[N: static int]( - f: var Fp12[BLS12_381], - Qs: array[N, ECP_ShortW_Aff[Fp2[BLS12_381], OnTwist]], - Ps: array[N, ECP_ShortW_Aff[Fp[BLS12_381], NotOnTwist]] - ) {.meter.} = - ## Generic Miller Loop for BLS12 curve - ## Computes f{u,Q}(P) with u the BLS curve parameter - - var Ts {.noInit.}: array[N, ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist]] - - # Ate param addition chain - # Hex: 0xd201000000010000 - # Bin: 0b1101001000000001000000000000000000000000000000010000000000000000 - - var iter = 1'u64 - - f.miller_first_iter(Ts, Qs, Ps) # 0b11 - f.miller_accum_doublings(Ts, Ps, 2) # 0b1100 - f.miller_accum_addition(Ts, Qs, Ps) # 0b1101 - f.miller_accum_doublings(Ts, Ps, 3) # 0b1101000 - f.miller_accum_addition(Ts, Qs, Ps) # 0b1101001 - f.miller_accum_doublings(Ts, Ps, 9) # 0b1101001000000000 - f.miller_accum_addition(Ts, Qs, Ps) # 0b1101001000000001 - f.miller_accum_doublings(Ts, Ps, 32) # 0b110100100000000100000000000000000000000000000000 - f.miller_accum_addition(Ts, Qs, Ps) # 0b110100100000000100000000000000000000000000000001 - f.miller_accum_doublings(Ts, Ps, 16) # 0b1101001000000001000000000000000000000000000000010000000000000000 - - # TODO: what is the threshold for Karabina's compressed squarings? diff --git a/research/kzg_poly_commit/fft_fr.nim b/research/kzg_poly_commit/fft_fr.nim index 5ef5153..2d89a59 100644 --- a/research/kzg_poly_commit/fft_fr.nim +++ b/research/kzg_poly_commit/fft_fr.nim @@ -56,7 +56,7 @@ type FFTS_TooManyValues = "Input length greater than the field 2-adicity (number of roots of unity)" FFTS_SizeNotPowerOfTwo = "Input must be of a power of 2 length" - FFTDescriptor[F] = object + FFTDescriptor*[F] = object ## Metadata for FFT on field F maxWidth: int rootOfUnity: F diff --git a/research/kzg_poly_commit/fft_g1.nim b/research/kzg_poly_commit/fft_g1.nim index f256e72..fe8a062 100644 --- a/research/kzg_poly_commit/fft_g1.nim +++ b/research/kzg_poly_commit/fft_g1.nim @@ -62,7 +62,7 @@ type FFTS_TooManyValues = "Input length greater than the field 2-adicity (number of roots of unity)" FFTS_SizeNotPowerOfTwo = "Input must be of a power of 2 length" - FFTDescriptor[EC] = object + FFTDescriptor*[EC] = object ## Metadata for FFT on Elliptic Curve maxWidth: int rootOfUnity: matchingOrderBigInt(EC.F.C) diff --git a/research/kzg_poly_commit/kzg_single_proofs.nim b/research/kzg_poly_commit/kzg_single_proofs.nim new file mode 100644 index 0000000..138615a --- /dev/null +++ b/research/kzg_poly_commit/kzg_single_proofs.nim @@ -0,0 +1,82 @@ +# https://github.com/ethereum/research/blob/master/kzg_data_availability/kzg_proofs.py + +import + ../../constantine/config/curves, + ../../constantine/[arithmetic, primitives, towers], + ../../constantine/elliptic/[ + ec_scalar_mul, + ec_shortweierstrass_affine, + ec_shortweierstrass_projective, + ], + ../../constantine/io/[io_fields, io_ec], + ../../constantine/pairing/[ + pairing_bls12, + miller_loops + ], + # Research + ./polynomials, + ./fft_fr + +type + G1 = ECP_ShortW_Prj[Fp[BLS12_381], NotOnTwist] + G2 = ECP_ShortW_Prj[Fp2[BLS12_381], OnTwist] + + KZGDescriptor = object + fftDesc: FFTDescriptor[Fr[BLS12_381]] + # [b.multiply(b.G1, pow(s, i, MODULUS)) for i in range(WIDTH+1)] + secretG1: seq[G1] + extendedSecretG1: seq[G1] + # [b.multiply(b.G2, pow(s, i, MODULUS)) for i in range(WIDTH+1)] + secretG2: seq[G2] + +var Generator1: ECP_ShortW_Aff[Fp[BLS12_381], NotOnTwist] +doAssert Generator1.fromHex( + "0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" +) + +var Generator2: ECP_ShortW_Aff[Fp2[BLS12_381], OnTwist] +doAssert Generator2.fromHex( + "0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + "0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e", + "0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801", + "0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be" +) + +func init( + T: type KZGDescriptor, + fftDesc: FFTDescriptor[Fr[BLS12_381]], + secretG1: seq[G1], secretG2: seq[G2] + ): T = + result.fftDesc = fftDesc + result.secretG1 = secretG1 + result.secretG2 = secretG2 + +func commitToPoly(kzg: KZGDescriptor, r: var G1, poly: openarray[Fr[BLS12_381]]) = + ## KZG commitment to polynomial in coefficient form + r.linear_combination(kzg.secretG1, poly) + +proc checkProofSingle( + kzg: KZGDescriptor, + commitment: G1, + proof: G1, + x, y: Fr[BLS12_381] + ): bool = + ## Check a proof for a Kate commitment for an evaluation f(x) = y + var xG2, g2: G2 + g2.projectiveFromAffine(Generator2) + xG2 = g2 + xG2.scalarMul(x.toBig()) + + var s_minus_x: G2 # s is a secret coefficient from the trusted setup (? to be confirmed) + s_minus_x.diff(kzg.secretG2[1], xG2) + + var yG1: G1 + yG1.projectiveFromAffine(Generator1) + yG1.scalarMul(y.toBig()) + + var commitment_minus_y: G1 + commitment_minus_y.diff(commitment, yG1) + + # Verify that e(commitment - [y]G1, Generator2) == e(proof, s - [x]G2) + return pair_verify(commitment_minus_y, g2, proof, s_minus_x) diff --git a/research/kzg_poly_commit/polynomials.nim b/research/kzg_poly_commit/polynomials.nim index 8bab5fc..ae88917 100644 --- a/research/kzg_poly_commit/polynomials.nim +++ b/research/kzg_poly_commit/polynomials.nim @@ -1,14 +1,16 @@ import ../../constantine/config/curves, - ../../constantine/[arithmetic, primitives], + ../../constantine/[arithmetic, primitives, towers], ../../constantine/elliptic/[ ec_scalar_mul, + ec_shortweierstrass_affine, ec_shortweierstrass_projective, ], ../../constantine/io/[io_fields, io_ec], - ../../constantine/pairings/[ - pairings_bls12, - miller_loops + ../../constantine/pairing/[ + pairing_bls12, + miller_loops, + cyclotomic_fp12 ] type @@ -19,7 +21,7 @@ type GT = Fp12[BLS12_381] func linear_combination*( - r: var , + r: var G1, points: openarray[G1], coefs: openarray[Fr[BLS12_381]] ) = @@ -63,5 +65,7 @@ func pair_verify*( gt2.millerLoopAddchain(Q2a, P2a) gt1 *= gt2 - gt.finalExpEasy() - gt.finalExpHard_BLS12() + gt1.finalExpEasy() + gt1.finalExpHard_BLS12() + + return gt1.isOne().bool()