From 18ef54f267d9bf190efe6381ea7979ade4763c23 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Mon, 5 Aug 2024 19:47:52 +0200 Subject: [PATCH 01/49] WIP commit of our groth16 work --- constantine/math/arithmetic/finite_fields.nim | 13 + constantine/math/io/io_ec.nim | 25 ++ constantine/math/io/io_extfields.nim | 34 +++ constantine/math/polynomials/fft.nim | 3 +- constantine/math/polynomials/fft_fields.nim | 239 +++++++++++++++ .../polynomials/fft_finite_field_test.nim | 162 +++++++++++ constantine/math/polynomials/fft_lut.nim | 140 +++++++++ .../named/config_fields_and_curves.nim | 20 ++ .../constraint_systems/r1cs_circom_parser.nim | 21 ++ .../constraint_systems/wtns_binary_parser.nim | 167 +++++++++++ .../constraint_systems/zkey_binary_parser.nim | 274 ++++++++++++++++++ 11 files changed, 1097 insertions(+), 1 deletion(-) create mode 100644 constantine/math/polynomials/fft_fields.nim create mode 100644 constantine/math/polynomials/fft_finite_field_test.nim create mode 100644 constantine/math/polynomials/fft_lut.nim create mode 100644 constantine/proof_systems/constraint_systems/wtns_binary_parser.nim create mode 100644 constantine/proof_systems/constraint_systems/zkey_binary_parser.nim diff --git a/constantine/math/arithmetic/finite_fields.nim b/constantine/math/arithmetic/finite_fields.nim index 7e18eca2e..3c1f07e8d 100644 --- a/constantine/math/arithmetic/finite_fields.nim +++ b/constantine/math/arithmetic/finite_fields.nim @@ -692,6 +692,19 @@ func pow_vartime*(r: var FF, a: FF, exponent: BigInt or openArray[byte] or FF) = # Small vartime exponentiation # ------------------------------------------------------------------- +func pow_vartime*(a: var FF, exponent: FF) = + ## Exponentiation modulo p + ## ``a``: a field element to be exponentiated + ## ``exponent``: a field element + ## + ## Warning ⚠️ : + ## This is an optimization for public exponent + ## Otherwise bits of the exponent can be retrieved with: + ## - memory access analysis + ## - power analysis + ## - timing analysis + a.pow_vartime(toBig exponent) + func pow_squareMultiply_vartime(a: var FF, exponent: SomeUnsignedInt) {.tags:[VarTime], meter.} = ## **Variable-time** Exponentiation ## diff --git a/constantine/math/io/io_ec.nim b/constantine/math/io/io_ec.nim index 2b04ab10f..0cff80a5f 100644 --- a/constantine/math/io/io_ec.nim +++ b/constantine/math/io/io_ec.nim @@ -56,6 +56,31 @@ func toHex*[EC: EC_ShortW_Prj or EC_ShortW_Jac or EC_ShortW_Aff or EC_ShortW_Jac result.appendHex(aff.y) result &= "\n" & sp & ")" +func toDecimal*[EC: EC_ShortW_Prj or EC_ShortW_Jac or EC_ShortW_Aff or EC_ShortW_JacExt](P: EC, indent: static int = 0): string = + ## Stringify an elliptic curve point to Hex + ## Note. Leading zeros are not removed. + ## Output as decimal. + ## + ## WARNING: NOT constant time! + ## + ## This proc output may change format in the future + + var aff {.noInit.}: EC_ShortW_Aff[EC.F, EC.G] + when EC isnot EC_ShortW_Aff: + aff.affine(P) + else: + aff = P + + const sp = spaces(indent) + + result = sp & $EC & "(\n" & sp & " x: " + result.add toDecimal(aff.x) + result &= ",\n" & sp & " y: " + result.add toDecimal(aff.y) + result &= "\n" & sp & ")" + + + func toHex*[EC: EC_TwEdw_Aff or EC_TwEdw_Prj](P: EC, indent: static int = 0): string = ## Stringify an elliptic curve point to Hex for Twisted Edwards Curve ## Note, leading zeros are not removed. diff --git a/constantine/math/io/io_extfields.nim b/constantine/math/io/io_extfields.nim index ab709e65a..e48848270 100644 --- a/constantine/math/io/io_extfields.nim +++ b/constantine/math/io/io_extfields.nim @@ -52,6 +52,40 @@ func toHex*(f: ExtensionField, indent = 0, order: static Endianness = bigEndian) ## - no leaks result.appendHex(f, indent, order) +func appendDecimal*(accum: var string, f: Fp, indent = 0, order: static Endianness = bigEndian) = + accum.add toDecimal(f) + +func appendDecimal*(accum: var string, f: ExtensionField, indent = 0, order: static Endianness = bigEndian) = + ## Stringify a tower field element to hex. + ## Note. Leading zeros are not removed. + ## Result is prefixed with 0x + ## + ## Output will be padded with 0s to maintain constant-time. + ## + ## CT: + ## - no leaks + accum.add static($f.typeof.genericHead() & '(') + staticFor i, 0, f.coords.len: + when i != 0: + accum.add ", " + accum.add "\n" & spaces(indent+2) & "c" & $i & ": " + when f is Fp2: + accum.appendDecimal(f.coords[i], order = order) + else: + accum.appendDecimal(f.coords[i], indent+2, order) + accum.add ")" + +func toDecimal*(f: ExtensionField, indent = 0, order: static Endianness = bigEndian): string = + ## Stringify a tower field element to hex. + ## Note. Leading zeros are not removed. + ## Result is prefixed with 0x + ## + ## Output will be padded with 0s to maintain constant-time. + ## + ## CT: + ## - no leaks + result.appendDecimal(f, indent, order) + func fromHex*(dst: var Fp2, c0, c1: string) = ## Convert 2 coordinates to an element of 𝔽p2 ## with dst = c0 + β * c1 diff --git a/constantine/math/polynomials/fft.nim b/constantine/math/polynomials/fft.nim index 6efc8e118..54a0c04e0 100644 --- a/constantine/math/polynomials/fft.nim +++ b/constantine/math/polynomials/fft.nim @@ -98,7 +98,8 @@ func fft_internal[EC; bits: static int]( for i in 0 ..< half: # FFT Butterfly - y_times_root .scalarMul_vartime(output[i+half], rootsOfUnity[i]) + y_times_root .scalarMul_vartime(rootsOfUnity[i], output[i+half]) + ## XXX: is this correct or need to be args exchanged? output[i+half] .diff_vartime(output[i], y_times_root) output[i] .sum_vartime(output[i], y_times_root) diff --git a/constantine/math/polynomials/fft_fields.nim b/constantine/math/polynomials/fft_fields.nim new file mode 100644 index 000000000..516a3b7d7 --- /dev/null +++ b/constantine/math/polynomials/fft_fields.nim @@ -0,0 +1,239 @@ +# 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 + constantine/named/algebras, + constantine/math/arithmetic, + constantine/math/io/io_bigints, + constantine/platforms/[abstractions, allocs, views], + ./fft_lut, + constantine/platforms/bithacks # for nextPowerOf2 + +# ############################################################ +# +# Fast Fourier Transform +# +# ############################################################ + +# Fast Fourier Transform (Number Theoretic Transform - NTT) over finite fields +# ---------------------------------------------------------------- + +type + FFTStatus* = enum + FFTS_Success + 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" + + FFT_Descriptor*[F] = object # `F` is either `Fp[Name]` or `Fr[Name]` + ## Metadata for FFT on Elliptic Curve + order*: int + rouGen*: F #getBigInt(F) + rootsOfUnity*: ptr UncheckedArray[getBigInt(F)] # `getBigInt` gives us the right type depending on Fr/Fp + ## domain, starting and ending with 1, length is cardinality+1 + ## This allows FFT and inverse FFT to use the same buffer for roots. + +func computeRootsOfUnity[F](ctx: var FFT_Descriptor[F], generatorRootOfUnity: auto) = + static: + doAssert typeof(generatorRootOfUnity) is Fr[F.Name] or typeof(generatorRootOfUnity) is Fp[F.Name] + + ctx.rootsOfUnity[0].setOne() + + debugecho "Generator ROU: ", generatorRootOfUnity.toHex() + var res = generatorRootOfUnity + let p = getBigInt(F).fromDecimal($ctx.order) + pow(res, p) + debugecho "To pow ? ", res.toHex() + + var cur = generatorRootOfUnity + for i in 1 .. ctx.order: + ctx.rootsOfUnity[i].fromField(cur) + cur *= generatorRootOfUnity + + doAssert ctx.rootsOfUnity[ctx.order].isOne().bool(), "The given generator does not seem to be a root of unity " & + "of " & $F & " for order: " & $ctx.order & "." + +func init*[Name: static Algebra](T: type FFT_Descriptor, order: int, generatorRootOfUnity: FF[Name]): T = + result.order = order + result.rouGen = generatorRootOfUnity + result.rootsOfUnity = allocHeapArrayAligned(T.F.getBigInt(), order+1, alignment = 64) + + result.computeRootsOfUnity(generatorRootOfUnity) + + for i in 0 ..< result.order: + debugecho "ω^", i, " = ", result.rootsOfUnity[i].toHex() + +proc rootOfUnityGenerator*[F](_: typedesc[F], order: int): F = + ## `p` = prime of the field (or order of subgroup) + ## `n` = FFT order + ## Highlighted the part we compute in each comment. + # ω = g^( `(p - 1)` // n ) + var exponentI {.noInit.}: BigInt[F.bits()] + exponentI = F.getModulus() + exponentI -= One + var exponent = F.fromBig(exponentI) + + # ω = g^( (p - 1) // `n` ) + var n = F.fromInt(order.uint64) + #echo "n = ", n.toHex() + # ω = g^( (p - 1) `// n` ) + n.inv() + #echo "Inverted? ", n.toHex() + # ω = g^( `(p - 1) // n` ) + exponent *= n + #echo "Exp? ", exponent.toHex() + + var g: F = F.fromUint(primitiveRoot(F.Name).uint64) + # ω = `g^( (p - 1) // n` ) + g.pow_vartime(toBig(exponent)) + #echo "g ? ", g.toHex() + result = g + +proc init*(T: typedesc[FFT_Descriptor], order: int): T = + ## For example for GF(13) and n == 4: + ## In backticks the part we currently compute + let g = rootOfUnityGenerator(T.F, order) + #let g2 = scaleToRootOfUnity(T.F.Name) # [28 - order] + #for i, el in g2: + # debugecho i, " = ", el.toHex() + result = T.init(order, g) + +func delete*(ctx: FFT_Descriptor) = + ctx.rootsOfUnity.freeHeapAligned() + +proc toFr[S: static int, Name: static Algebra](x: BigInt[S], isMont = true): Fr[Name] = + result.fromBig(x) + +proc toFp[S: static int, Name: static Algebra](x: BigInt[S], isMont = true): Fp[Name] = + result.fromBig(x) + +proc toF[F; S: static int](T: typedesc[F], x: BigInt[S]): auto = + when T is Fr: + toFr[S, T.Name](x) + else: + toFp[S, T.Name](x) + +func simpleFT[F; bits: static int]( + output: var StridedView[F], + vals: StridedView[F], + rootsOfUnity: StridedView[BigInt[bits]]) = + # FFT is a recursive algorithm + # This is the base-case using a O(n²) algorithm + + let L = output.len + var last {.noInit.}, v {.noInit.}: F + + var v0w0 {.noinit}: F + var v0w0In {.noInit.} = vals[0] + static: echo "TYE ??? ", F.Name, " is fp ? ", F is Fp[Fake13] + ## XXX: THER IS NO `prod` WITH ONLY 1 EXTRA ARG + v0w0.prod(v0w0In, F.toF(rootsOfUnity[0])) + + for i in 0 ..< L: + last = v0w0 + for j in 1 ..< L: + v.prod(F.toF(rootsOfUnity[(i*j) mod L]), vals[j]) + last.sum(last, v) + output[i] = last + +func fft_internal[F; bits: static int]( + output: var StridedView[F], + vals: StridedView[F], + rootsOfUnity: StridedView[BigInt[bits]]) = + if output.len <= 4: + simpleFT(output, vals, rootsOfUnity) + return + + let (evenVals, oddVals) = vals.splitAlternate() + var (outLeft, outRight) = output.splitHalf() + let halfROI = rootsOfUnity.skipHalf() + + fft_internal(outLeft, evenVals, halfROI) + fft_internal(outRight, oddVals, halfROI) + + let half = outLeft.len + var y_times_root{.noinit.}: F + + for i in 0 ..< half: + # FFT Butterfly + y_times_root.prod(F.toF(rootsOfUnity[i]), output[i+half]) + output[i+half].diff(output[i], y_times_root) + output[i].sum(output[i], y_times_root) + + +func fft_vartime*[F]( + desc: FFT_Descriptor[F], + output: var openarray[F], + vals: openarray[F]): FFT_Status = + if vals.len > desc.order: + return FFTS_TooManyValues + if not vals.len.uint64.isPowerOf2_vartime(): + return FFTS_SizeNotPowerOfTwo + + let rootz = desc.rootsOfUnity + .toStridedView(desc.order) + .slice(0, desc.order-1, desc.order div vals.len) + + var voutput = output.toStridedView() + fft_internal(voutput, vals.toStridedView(), rootz) + return FFTS_Success + +# Similar adjustments would be made for ifft_vartime + +func ifft_vartime*[F]( + desc: FFT_Descriptor[F], + output: var openarray[F], + vals: openarray[F]): FFT_Status = + ## Inverse FFT + if vals.len > desc.order: + return FFTS_TooManyValues + if not vals.len.uint64.isPowerOf2_vartime(): + return FFTS_SizeNotPowerOfTwo + + let rootz = desc.rootsOfUnity + .toStridedView(desc.order+1) # Extra 1 at the end so that when reversed the buffer starts with 1 + .reversed() + .slice(0, desc.order-1, desc.order div vals.len) + + var voutput = output.toStridedView() + fft_internal(voutput, vals.toStridedView(), rootz) + + var invLen {.noInit.}: F.getBigInt() + invLen.fromUint(vals.len.uint64) + invLen.invmod_vartime(invLen, F.getModulus()) + + for i in 0 ..< output.len: + let inp = output[i] + output[i].prod(inp, F.toF(invLen)) + + return FFTS_Success + +func fft_vartime*[F](vals: openarray[F]): seq[F] = + ## Performs an FFT on the given values and returns a seq of the result. + ## + ## For convenience only! + let order = nextPowerOfTwo_vartime(vals.len.uint64) + var fftDesc = FFTDescriptor[F].init(order.int) + defer: fftDesc.delete() + + result = newSeq[F](order) + let status = fftDesc.fft_vartime(result, vals) + + doAssert (status == FFTS_Success).bool, "FFT failed." + +proc ifft_vartime*[F](vals: openarray[F]): seq[F] = + ## Performs an inverse FFT on the given values and returns a seq of the result. + ## + ## For convenience only! + let order = nextPowerOfTwo_vartime(vals.len.uint64) + var fftDesc = FFTDescriptor[F].init(order.int) + defer: fftDesc.delete() + + result = newSeq[F](order) + let status = fftDesc.ifft_vartime(result, vals) + + doAssert (status == FFTS_Success).bool, "FFT failed." diff --git a/constantine/math/polynomials/fft_finite_field_test.nim b/constantine/math/polynomials/fft_finite_field_test.nim new file mode 100644 index 000000000..8a4f7be94 --- /dev/null +++ b/constantine/math/polynomials/fft_finite_field_test.nim @@ -0,0 +1,162 @@ +import + unittest, + constantine/math/arithmetic, + constantine/math/io/io_fields, + #constantine/math/elliptic/ec_shortweierstrass, + #constantine/named/zoo_generators, + constantine/named/[algebras, properties_fields, properties_curves], + constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], + #constantine/math/constants/zoo_fields, + #constantine/math/constants/zoo_subgroups, + #constantine/protocols/bls_signatures, + # Assume the FFT implementations are in these modules: + ./fft_fields, + ./fft + #fft_elliptic_curve + +type + EC = EC_ShortW_Jac[Fp[BLS6_6], G1] + + +#echo getBigInt(F.Name, kBaseField) + +suite "FFT Tests": + test "FFT over Finite Field Fp[Fake13]": + + type F = Fp[Fake13] + + # Fr[Fake13] is GF(13) + let order = 4 # We'll use a small order for the test + # `5` is a generator for the order `4`, because `5^4 mod 13 = 1`: + # `5^0 mod 13 = 1 ` + # `5^1 mod 13 = 5 ` + # `5^2 mod 13 = 12` + # `5^3 mod 13 = 8 ` + # `5^4 mod 13 = 1 ` + let Gen = F.fromUInt(5'u64) + var fftDesc = FFTDescriptor[Fp[Fake13]].init(order, Gen) + defer: fftDesc.delete() + + # Input values + # Describes Polynomial: + # P(x) = a₀ + a₁·x + a₂·x² + a₃·x³ + # P(x) = 1 + 2·x + 3·x² + 4·x³ + var input = @[F.fromInt(1), F.fromInt(2), F.fromInt(3), F.fromInt(4)] + var output = newSeq[F](order) + + # Perform forward FFT + # The forward FFT returns the evaluation of polynomial P(x) at the + # roots of unity based on `Gen` + let status = fftDesc.fft_vartime(output, input) + check (status == FFTS_Success).bool + + # Expected output (calculated manually or with a known-good implementation) + let expected = @[ + # let `g(i) = 5^i mod 13` # where `5` is the generator, i.e. `5^4 mod 13 = 1` + F.fromUInt(10'u64), # P(g(0)) = (1 + 2·1 + 3·1² + 4·1³ ) mod 13 = 10 + F.fromUInt(1'u64), # P(g(1)) = (1 + 2·5 + 3·5² + 4·5³ ) mod 13 = 1 + F.fromUInt(11'u64), # P(g(2)) = (1 + 2·12 + 3·12² + 4·12³) mod 13 = 11 + F.fromUInt(8'u64) # P(g(3)) = (1 + 2·8 + 3·8² + 4·8³ ) mod 13 = 8 + ] + + for i in 0 ..< output.len: + echo "Output: ", output[i].toHex() + echo "Expect: ", expected[i].toHex() + check (output[i] == expected[i]).bool + + # Perform inverse FFT + var inverse_output = newSeq[F](order) + let inverse_status = fftDesc.ifft_vartime(inverse_output, output) + check (inverse_status == FFTS_Success).bool + + # Check if we get back the original input + for i in 0 ..< order: + echo "Inverse results in: ", inverse_output[i].toDecimal() + check (inverse_output[i] == input[i]).bool + + test "FFT over finite field Fr[BN254_Snarks]": + type F = Fr[BN254_Snarks] + + const order = 4 + var fftDesc = FFTDescriptor[Fr[BN254_Snarks]].init(order) + defer: fftDesc.delete() + + + echo fftDesc.rouGen.toHex() + + # Input values + # Describes Polynomial: + # P(x) = a₀ + a₁·x + a₂·x² + a₃·x³ + # P(x) = 1 + 2·x + 3·x² + 4·x³ + var input = @[F.fromInt(1), F.fromInt(2), F.fromInt(3), F.fromInt(4)] + var output = newSeq[F](order) + + # Perform forward FFT + # The forward FFT returns the evaluation of polynomial P(x) at the + # roots of unity based on `Gen` + let status = fftDesc.fft_vartime(output, input) + check (status == FFTS_Success).bool + + for i in 0 ..< output.len: + echo "Output: ", output[i].toHex() + #echo "Expect: ", expected[i].toHex() + #check (output[i] == expected[i]).bool + + # Perform inverse FFT + var inverse_output = newSeq[F](order) + let inverse_status = fftDesc.ifft_vartime(inverse_output, output) + check (inverse_status == FFTS_Success).bool + + # Check if we get back the original input + for i in 0 ..< order: + echo "Inverse results in: ", inverse_output[i].toDecimal() + check (inverse_output[i] == input[i]).bool + + +## XXX: Also broken! Same as `fft.nim` main part! +# test "FFT over Elliptic Curve EC[Fp[BLS6_6], G1]": +# proc getROU(): Fr[BLS6_6] = +# result = Fr[BLS6_6].fromUint(3'u32) +# +# proc getGenG1(): EC = +# let fx = Fp[BLS6_6].fromUint(13'u32) +# let fy = Fp[BLS6_6].fromUint(15'u32) +# var gen {.noinit.}: EC_ShortW_Aff[Fp[BLS6_6], G1] +# gen.x = fx +# gen.y = fy +# result = gen.getJacobian() +# +# +# let order = 4 # We'll use a small order for the test +# var fftDesc = ECFFTDescriptor[EC].new(order, getROU()) +# defer: fftDesc.delete() +# +# # Input values (multiples of the generator point) +# var input = newSeq[EC](order) +# let generator = getGenG1() #BLS6_6.getGenerator("G1") +# for i in 0 ..< order: +# input[i].scalarMul(Fr[BLS6_6].fromInt((i + 1).uint), generator) +# +# var output = newSeq[EC](order) +# +# # Perform forward EC-FFT +# let status = fftDesc.fft_vartime(output, input) +# check(status == FFTS_Success) +# +# # Expected output (calculated manually or with a known-good implementation) +# # Note: This would depend on the specific curve parameters and roots of unity used +# # For this test, we'll just check some properties instead of exact values +# +# # Check that the output points are on the curve +# for point in output: +# check(point.isOnCurve()) +# +# # Perform inverse EC-FFT +# var inverse_output = newSeq[EC](order) +# let inverse_status = fftDesc.ifft_vartime(inverse_output, output) +# check(inverse_status == FFTS_Success) +# +# # Check if we get back the original input +# for i in 0 ..< order: +# check(inverse_output[i] == input[i]) +# diff --git a/constantine/math/polynomials/fft_lut.nim b/constantine/math/polynomials/fft_lut.nim new file mode 100644 index 000000000..d1187a7bb --- /dev/null +++ b/constantine/math/polynomials/fft_lut.nim @@ -0,0 +1,140 @@ +# 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 + std/macros, + constantine/platforms/abstractions, + constantine/named/algebras, + constantine/math/arithmetic, + constantine/math/io/[io_fields, io_bigints] + +# TODO automate this +# we can precompute everything in Sage +# and auto-generate the file. + +# SageMath: +# `GF(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001).primitive_element()` +const BLS12_381_Fr_primitive_root = 7 + +# SageMath: +# `GF(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001).primitive_element()` +const BN254_Snarks_Fr_primitive_root = 5 + +#[ +def precomp_ts(Fq): + ## From q = p^m with p the prime characteristic of the field Fp^m + ## + ## Returns (s, e) such as + ## q == s * 2^e + 1 + s = Fq.order() - 1 + e = 0 + while s & 1 == 0: + s >>= 1 + e += 1 + return s, e + +def find_any_qnr(Fq): + ## Find a quadratic Non-Residue + ## in GF(p^m) + qnr = Fq(Fq.gen()) + r = Fq.order() + while qnr.is_square(): + qnr += 1 + return qnr + +s, e = precomp_ts(Fr) +qnr = find_any_qnr(Fr) +root_unity = qnr^s +]# + + +proc decomposeFieldOrder(F: type Fr): tuple[s: F, e: uint64] = + ## Decomposes the field order of the given field into the form + ## + ## `q = s * 2^e + 1` + ## + ## where `q = p^m` with `p` the prime characteristic of the field Fp^m. + # `s = p - 1` + var s {.noInit.}: BigInt[F.bits()] + s = F.getModulus() + debugecho "Exp: ", s.tohex() + s -= One + + # `e = 0` + var e: uint64 + + while s.isEven().bool: + s.shiftRight(1) + #e += F.fromUint(1'u64) + inc e + result = (s: F.fromBig(s), e: e) + +proc findAnyQnr(F: type Fr): F = + ## Returns the first quadratic non-residue found, i.e. a + ## number in `F`, which is not a square in the field. + ## + ## That is, a `q` if there is no `x` in the field such that: + ## `x² ≡ q (mod p)` + let one = F.fromUint(1'u64) + var qnr {.noInit.}: F + qnr = one # Start with `1` + while qnr.isSquare().bool: + qnr += one + result = qnr + +func buildRootLUT(F: type Fr, primitive_root: uint64): array[32, F] = + ## [pow(PRIMITIVE_ROOT, (MODULUS - 1) // (2**i), MODULUS) for i in range(32)] + + ## + ## XXX: For some reason this does not work for BN254_Snarks (haven't tried with BLS12-381) + + let (s, e) = decomposeFieldOrder(F) + let qnr = findAnyQnr(F) + + #var exponent {.noInit.}: BigInt[F.bits()] + #exponent = F.getModulus() + #debugecho "Exp: ", exponent.tohex() + #exponent -= One + # + ## Start by the end + #var i = result.len - 1 + #exponent.shiftRight(i) + #debugecho "last Exponent : ", exponent.toHex() + #result[i].fromUint(primitive_root) + #result[i].pow_vartime(exponent) + + + var i = e.int #e.toDecimal() # largest possible power of 2 for this field + var rootUnity = qnr + rootUnity.pow_vartime(s) + result[i] = rootUnity + + + while i > 0: + result[i-1].square(result[i]) + #debugecho "At ", i, ": ", result[i-1].toHex() + + dec i + + # debugEcho "Fr[BLS12_81] - Roots of Unity:" + # for i in 0 ..< result.len: + # debugEcho " ", i, ": ", result[i].toHex() + # debugEcho "Fr[BLS12_81] - Roots of Unity -- FIN\n" + +#let BLS12_381_Fr_ScaleToRootOfUnity* = buildRootLUT(Fr[BLS12_381], BLS12_381_Fr_primitive_root) +let BN254_Snarks_Fr_ScaleToRootOfUnity* = buildRootLUT(Fr[BN254_Snarks], BN254_Snarks_Fr_primitive_root) +let g2 = BN254_Snarks_Fr_ScaleToRootOfUnity # [28 - order] +for i, el in g2: + debugecho "ω^{2^", i, "} = ", el.toHex() + +{.experimental: "dynamicBindSym".} +macro scaleToRootOfUnity*(Name: static Algebra): untyped = + return bindSym($Name & "_Fr_ScaleToRootOfUnity") + +macro primitiveRoot*(Name: static Algebra): untyped = + return bindSym($Name & "_Fr_primitive_root") diff --git a/constantine/named/config_fields_and_curves.nim b/constantine/named/config_fields_and_curves.nim index b1024759e..9188c84ec 100644 --- a/constantine/named/config_fields_and_curves.nim +++ b/constantine/named/config_fields_and_curves.nim @@ -49,6 +49,26 @@ declareCurves: testingCurve: true bitwidth: 3 modulus: "0x5" + curve Fake13: + testingCurve: true + bitWidth: 4 + modulus: "0xd" + curve BLS6_6: + testingCurve: true + bitwidth: 6 + modulus: "0x2b" + family: BarretoLynnScott + order: "0x27" + orderBitwidth: 6 + eq_form: ShortWeierstrass + coef_a: 0 + coef_b: 6 + #nonresidue_fp: 5 # 5 is not a square in 𝔽p + #nonresidue_fp2: (0, 1) # √-5 √-5 is not a square or cube in 𝔽p² + + embedding_degree: 6 + sexticTwist: M_Twist + curve Fake101: testingCurve: true bitwidth: 7 diff --git a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim index 63d27de63..a2254b932 100644 --- a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim +++ b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim @@ -121,6 +121,27 @@ type R1csCustomGatesList* = object R1csCustomGatesApp* = object + R1CS* = object + magic*: array[4, char] + version*: uint32 + numberSections*: uint32 + header*: Header + constraints*: seq[Constraint] + w2l*: Wire2Label + + +proc toR1CS*(r1cs: R1csBin): R1CS = + result = R1CS(magic: r1cs.magic, + version: r1cs.version, + numberSections: r1cs.numberSections) + for s in r1cs.sections: + case s.sectionType + of kHeader: result.header = s.header + of kConstraints: result.constraints = s.constraints + of kWire2LabelId: result.w2l = s.w2l + else: + echo "Ignoring: ", s.sectionType + proc initSection(kind: R1csSectionKind, size: uint64): Section = result = Section(sectionType: kind, size: size) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim new file mode 100644 index 000000000..0a349b5ef --- /dev/null +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -0,0 +1,167 @@ +# 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. + +# This file implements serialization +# +import + ../../serialization/[io_limbs, parsing], + constantine/platforms/[fileio, abstractions] + +#[ +1. File Header: + - Magic String: + - Offset: 0 bytes + - Length: 4 bytes + - Content: ASCII string "wtns" + - Version Number: + - Offset: 4 bytes + - Length: 4 bytes + - Content: 32-bit unsigned integer indicating the format version (e.g., 2) + - Section Count: + - Offset: 8 bytes + - Length: 4 bytes + - Content: 32-bit unsigned integer indicating the number of sections (e.g., 1) + +2. Witness Length: + - Witness Count: + - Offset: 12 bytes + - Length: 8 bytes + - Content: 64-bit unsigned integer indicating the number of witness elements + +3. Witness Data: + - Witness Elements: + - Offset: 20 bytes + - Length: 32 bytes per element + - Content: Each witness element is a 256-bit (32 bytes) unsigned integer in Big Endian format +]# + +# We use `sortedByIt` to sort the different sections in the file by their +# `WtnsSectionKind` +from std / sequtils import filterIt +from std / algorithm import sortedByIt +from std / strutils import endsWith + +type + WtnsSectionKind* = enum # `kInvalid` used to indicate unset & to make the enum work as field discriminator + kInvalid = 0 + kHeader = 1 + kData = 2 + + WitnessHeader* = object + n8*: uint32 # field size in bytes + r*: seq[byte] # prime order of the field + num*: uint32 # number of witness elements + + Witness* = object + data*: seq[byte] + + Section* = object + size*: uint64 # NOTE: in the real file the section type is *FIRST* and then the size + # But we cannot map this with a variant type in Nim (without using different + # names for each variant branch) + case sectionType*: WtnsSectionKind + of kInvalid: discard + of kHeader: header*: WitnessHeader + of kData: wtns: seq[Witness] + + ## `WtnsBin` is binary compatible with an Witness binary file. Meaning it follows the structure + ## of the file (almost) exactly. The only difference is in the section header. The size comes + ## *after* the kind, which we don't reproduce in `Section` above + WtnsBin* = object + magic*: array[4, char] # "wtns" + version*: uint32 + numberSections*: uint32 + sections*: seq[Section] # Note: Because of the unordered nature of the sections + # the `sections` seq won't follow the binary order of + # the data in the file. Instead, we first record (kind, file position) + # of each different section in the file and then parse them in increasing + # order of the section types + +func header*(wtns: WtnsBin): WitnessHeader = + result = wtns.sections.filterIt(it.sectionType == kHeader)[0].header + +func witnesses*(wtns: WtnsBin): seq[Witness] = + result = wtns.sections.filterIt(it.sectionType == kData)[0].wtns + +proc initSection(kind: WtnsSectionKind, size: uint64): Section = + result = Section(sectionType: kind, size: size) + +template wtnsSection(sectionSize, body: untyped): untyped = + let startOffset = f.getFilePosition() + + body + + return sectionSize.int == f.getFilePosition() - startOffset + +proc parseMagicHeader(f: File, mh: var array[4, char]): bool = + result = f.readInto(mh) + +proc parseSectionKind(f: File, v: var WtnsSectionKind): bool = + var val: uint32 + result = f.parseInt(val, littleEndian) + v = WtnsSectionKind(val.int) + +proc parseWitnessHeader(f: File, h: var WitnessHeader): bool = + ?f.parseInt(h.n8, littleEndian) # byte size of the prime number + h.r.setLen(h.n8) + ?f.readInto(h.r) + ?f.parseInt(h.num, littleEndian) + result = true # would have returned before due to `?` otherwise + +proc parseWitnesses(f: File, s: var seq[Witness], sectionSize: uint64, elemSize: uint32): bool = + ## Parses the witnesses + let numElems = sectionSize div elemSize.uint64 + var buf = newSeq[byte](elemSize) + s.setLen(numElems) + for i in 0 ..< numElems: + ?f.readInto(buf) + s[i] = Witness(data: buf) ## XXX: fix me + result = true + +proc parseWitnesses(f: File, s: var Section, size: uint64, wtns: WtnsBin): bool = + let h = wtns.sections.filterIt(it.sectionType == kHeader)[0].header ## XXX: fixme + result = parseWitnesses(f, s.wtns, size, h.n8) + +proc parseSection(f: File, s: var Section, kind: WtnsSectionKind, size: uint64, wtns: var WtnsBin): bool = + # NOTE: The `wtns` object is there to provide the header information to + # the constraints section + s = initSection(kind, size) + case kind + of kHeader: ?f.parseWitnessHeader(s.header) + of kData: ?f.parseWitnesses(s, size, wtns) + else: raiseAssert "Invalid" + + result = true # would have returned otherwise due to `?` + +proc parseSection(f: File, wtns: var WtnsBin): Section = + var kind: WtnsSectionKind + var size: uint64 + doAssert f.parseSectionKind(kind), "Failed to read section type in section " + echo "Got section kind::: ", kind + + doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " + + result = initSection(kHeader, size) + + doAssert f.parseSection(result, kind, size, wtns), "Failed to parse section: " & $kind + +proc parseWtnsFile*(path: string): WtnsBin = + var f = fileio.open(path, kRead) + + doAssert f.parseMagicHeader(result.magic), "Failed to read magic header" + doAssert f.parseInt(result.version, littleEndian), "Failed to read version" + doAssert f.parseInt(result.numberSections, littleEndian), "Failed to read number of sections" + + for i in 0 ..< result.numberSections: + let s = parseSection(f, result) + result.sections.add s + + fileio.close(f) + +when isMainModule: + echo parseWtnsFile("/home/basti/org/constantine/moonmath/circom/three_fac_js/witness.wtns") diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim new file mode 100644 index 000000000..169159d92 --- /dev/null +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -0,0 +1,274 @@ +# 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. + +# This file implements serialization +# +import + ../../serialization/[io_limbs, parsing], + constantine/platforms/[fileio, abstractions] + +# We use `sortedByIt` to sort the different sections in the file by their +# `ZkeySectionKind` +from std / sequtils import filterIt +from std / algorithm import sortedByIt +from std / strutils import endsWith + +type + ZkeySectionKind* = enum # `kInvalid` used to indicate unset & to make the enum work as field discriminator + kInvalid = 0 + kHeader = 1 + kGroth16Header = 2 + kIC = 3 + kCoeffs = 4 + kA = 5 + kB1 = 6 + kB2 = 7 + kC = 8 + kH = 9 + kContributions = 10 + + Header* = object + proverType*: uint32 ## Must be `1` for Groth16 + + Groth16Header* = object + n8q*: uint32 # Size of base field in bytes (4 bytes, unsigned integer) + q*: seq[byte] # Prime of the base field (n8q bytes) + n8r*: uint32 # Size of scalar field in bytes (4 bytes, unsigned integer) + r*: seq[byte] # Prime of the scalar field (n8r bytes) + nVars*: uint32 # Total number of variables (4 bytes, unsigned integer) + nPublic*: uint32 # Number of public variables (4 bytes, unsigned integer) + domainSize*: uint32 # Size of the domain (4 bytes, unsigned integer) + alpha1*: seq[byte] # alpha in G1 (2 * n8q bytes) + beta1*: seq[byte] # beta in G1 (2 * n8q bytes) + beta2*: seq[byte] # beta in G2 (4 * n8q bytes) + gamma2*: seq[byte] # gamma in G2 (4 * n8q bytes) + delta1*: seq[byte] # delta in G1 (2 * n8q bytes) + delta2*: seq[byte] # delta in G2 (4 * n8q bytes) + + + ## Generic section containing multiple points on one of the curves. Will be unmarshaled into + ## points on the correct curve afterwars. + DataSection* = object + points*: seq[seq[byte]] + + IC* = DataSection + A* = DataSection + B1* = DataSection + B2* = DataSection + C* = DataSection + H* = DataSection + + Coefficient* = object + matrix*: uint32 + section*: uint32 + index*: uint32 + value*: seq[byte] # n8r bytes + + Coefficients* = object + num*: uint32 # number of coefficients + cs*: seq[Coefficient] + + Contributions* = object + hash*: array[64, byte] # hash of the circuit + num*: uint32 # number of contributions + # for each contribution has some data + + Section* = object + size*: uint64 # NOTE: in the real file the section type is *FIRST* and then the size + # But we cannot map this with a variant type in Nim (without using different + # names for each variant branch) + case sectionType*: ZkeySectionKind + of kInvalid: discard + of kHeader: header*: Header + of kGroth16Header: g16h: Groth16Header + of kIC: ic: IC + of kCoeffs: coeffs: Coefficients + of kA: a: A + of kB1: b1: B1 + of kB2: b2: B2 + of kC: c: C + of kH: h: H + of kContributions: contr: Contributions + + ## `ZkeyBin` is binary compatible with an R1CS binary file. Meaning it follows the structure + ## of the file (almost) exactly. The only difference is in the section header. The size comes + ## *after* the kind, which we don't reproduce in `Section` above + ZkeyBin* = object + magic*: array[4, char] + version*: uint32 + numberSections*: uint32 + sections*: seq[Section] # Note: Because of the unordered nature of the sections + # the `sections` seq won't follow the binary order of + # the data in the file. Instead, we first record (kind, file position) + # of each different section in the file and then parse them in increasing + # order of the section types + +func header*(zkey: ZkeyBin): Header = + result = zkey.sections.filterIt(it.sectionType == kHeader)[0].header + +func Afield*(zkey: ZkeyBin): A = + result = zkey.sections.filterIt(it.sectionType == kA)[0].a + +func B1field*(zkey: ZkeyBin): B1 = + result = zkey.sections.filterIt(it.sectionType == kB1)[0].b1 + +func B2field*(zkey: ZkeyBin): B2 = + result = zkey.sections.filterIt(it.sectionType == kB2)[0].b2 + +func Cfield*(zkey: ZkeyBin): C = + result = zkey.sections.filterIt(it.sectionType == kC)[0].c + +func Hfield*(zkey: ZkeyBin): H = + result = zkey.sections.filterIt(it.sectionType == kH)[0].h + +func coeffs*(zkey: ZkeyBin): Coefficients = + result = zkey.sections.filterIt(it.sectionType == kCoeffs)[0].coeffs + +func groth16Header*(zkey: ZkeyBin): Groth16Header = + result = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h + + +proc initSection(kind: ZkeySectionKind, size: uint64): Section = + result = Section(sectionType: kind, size: size) + +template zkeySection(sectionSize, body: untyped): untyped = + let startOffset = f.getFilePosition() + + body + + return sectionSize.int == f.getFilePosition() - startOffset + +proc parseMagicHeader(f: File, mh: var array[4, char]): bool = + result = f.readInto(mh) + +proc parseSectionKind(f: File, v: var ZkeySectionKind): bool = + var val: uint32 + result = f.parseInt(val, littleEndian) + v = ZkeySectionKind(val.int) + +proc parseHeader(f: File, h: var Header): bool = + ?f.parseInt(h.proverType, littleEndian) # byte size of the prime number + doAssert h.proverType == 1, "Prover type must be `1` for Groth16, found: " & $h.proverType + result = true # would have returned before due to `?` otherwise + +proc parseGroth16Header(f: File, g16h: var Groth16Header): bool = + var buf: seq[byte] + for field, v in fieldPairs(g16h): + when typeof(v) is uint32: + ?f.parseInt(v, littleEndian) + elif typeof(v) is seq[byte]: + when field == "q": + doAssert g16h.n8q != 0, "Parsing failure, `n8q` not parsed." + buf.setLen(g16h.n8q) + elif field == "r": + doAssert g16h.n8r != 0, "Parsing failure, `n8r` not parsed." + buf.setLen(g16h.n8r) + elif field.endsWith("1"): # 2 * n8q bytes + doAssert g16h.n8q != 0, "Parsing failure, `n8q` not parsed." + buf.setLen(2 * g16h.n8q) + elif field.endsWith("2"): # 4 * n8q bytes + doAssert g16h.n8q != 0, "Parsing failure, `n8q` not parsed." + buf.setLen(4 * g16h.n8q) + else: + raiseAssert "Unsupported field: " & $field + ?f.readInto(buf) + v = buf + else: + raiseAssert "Unsupported type: " & $typeof(v) + result = true + +proc parseDataSection(f: File, d: var DataSection, sectionSize: uint64, elemSize: uint32): bool = + ## Parses a generic data section, each `elemSize` in size + let numElems = sectionSize div elemSize.uint64 + var buf = newSeq[byte](elemSize) + d.points.setLen(numElems.int) + for i in 0 ..< numElems: + ?f.readInto(buf) + d.points[i] = buf ## XXX: fix me + result = true + +proc parseDatasection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: var ZkeyBin): bool = + let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme + echo "Parsing data section: ", kind + case kind + of kIC: ?f.parseDataSection(s.ic, size, 2 * g16h.n8q) + of kA: ?f.parseDataSection(s.a, size, 2 * g16h.n8q) + of kB1: ?f.parseDataSection(s.b1, size, 2 * g16h.n8q) + of kB2: ?f.parseDataSection(s.b2, size, 4 * g16h.n8q) + of kC: ?f.parseDataSection(s.c, size, 2 * g16h.n8q) + of kH: ?f.parseDataSection(s.h, size, 2 * g16h.n8q) + else: + raiseAssert "Not a data section: " & $kind + result = true + +proc parseCoefficient(f: File, s: var Coefficient, size: uint64): bool = + ?f.parseInt(s.matrix, littleEndian) + ?f.parseInt(s.section, littleEndian) + ?f.parseInt(s.index, littleEndian) + s.value = newSeq[byte](size) + ?f.readInto(s.value) + result = true + +proc parseCoefficients(f: File, s: var Coefficients, zkey: ZkeyBin): bool = + ?f.parseInt(s.num, littleEndian) + let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme + s.cs = newSeq[Coefficient](s.num) + for i in 0 ..< s.num: # parse coefficients + echo "Parsing coefficient with : ", g16h.n8r, " bytes" + ?f.parseCoefficient(s.cs[i], g16h.n8r) + result = true + +proc parseContributions(f: File, s: var Contributions): bool = + ?f.readInto(s.hash) + ?f.parseInt(s.num, littleEndian) + # XXX: parse individual contributions + result = true + +proc parseSection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: var ZkeyBin): bool = + # NOTE: The `zkey` object is there to provide the header information to + # the constraints section + s = initSection(kind, size) + case kind + of kHeader: ?f.parseHeader(s.header) + of kGroth16Header: ?f.parseGroth16Header(s.g16h) + of kIC, kA .. kH: ?f.parseDataSection(s, kind, size, zkey) + of kCoeffs: ?f.parseCoefficients(s.coeffs, zkey) + of kContributions: ?f.parseContributions(s.contr) + else: raiseAssert "Invalid" + + result = true # would have returned otherwise due to `?` + +proc parseSection(f: File, zkey: var ZkeyBin): Section = + var kind: ZkeySectionKind + var size: uint64 + doAssert f.parseSectionKind(kind), "Failed to read section type in section " + echo "Got section kind::: ", kind + + doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " + + result = initSection(kHeader, size) + + doAssert f.parseSection(result, kind, size, zkey), "Failed to parse section: " & $kind + +proc parseZkeyFile*(path: string): ZkeyBin = + var f = fileio.open(path, kRead) + + doAssert f.parseMagicHeader(result.magic), "Failed to read magic header" + doAssert f.parseInt(result.version, littleEndian), "Failed to read version" + doAssert f.parseInt(result.numberSections, littleEndian), "Failed to read number of sections" + + for i in 0 ..< result.numberSections: + let s = parseSection(f, result) + result.sections.add s + + fileio.close(f) + +when isMainModule: + #echo parseZkeyFile("/tmp/test.zkey") + #echo parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac0000.zkey") + echo parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") From 79445dfb89dff57b0027cda1dd5f17ffba802d4d Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 12:55:38 +0200 Subject: [PATCH 02/49] add partially manual Groth16 proof file --- .../constraint_systems/groth16_utils.nim | 63 +++ .../constraint_systems/manual_groth16.nim | 369 ++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 constantine/proof_systems/constraint_systems/groth16_utils.nim create mode 100644 constantine/proof_systems/constraint_systems/manual_groth16.nim diff --git a/constantine/proof_systems/constraint_systems/groth16_utils.nim b/constantine/proof_systems/constraint_systems/groth16_utils.nim new file mode 100644 index 000000000..1a3c15c55 --- /dev/null +++ b/constantine/proof_systems/constraint_systems/groth16_utils.nim @@ -0,0 +1,63 @@ +import ../../math/[arithmetic, extension_fields] +import ../../math/io/[io_bigints, io_fields, io_ec, io_extfields] +import ../../platforms/abstractions +import ../../named/[algebras, properties_fields, properties_curves] +import ../../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime] + +## NOTE: These constructors for ... +proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = + let b = matchingBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian) + if isMont: + var bN: typeof(b) + bN.fromMont(b, Fp[Name].getModulus(), Fp[Name].getNegInvModWord(), Fp[Name].getSpareBits()) + result.fromBig(bN) + else: + result.fromBig(b) + +proc toFr*[Name: static Algebra](x: seq[byte], isMont = true, isDoubleMont = false): Fr[Name] = + let b = matchingOrderBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian) + if isMont: + var bN: typeof(b) + bN.fromMont(b, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) + result.fromBig(bN) + elif isDoubleMont: + var bN: typeof(b) + bN.fromMont(b, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) + var bNN: typeof(b) + bNN.fromMont(bN, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) + result.fromBig(bNN) + else: + result.fromBig(b) + +proc toEcG1*[Name: static Algebra](s: seq[byte]): EC_ShortW_Aff[Fp[Name], G1] = + let x = toFp[Name](s[0 .. 31]) + let y = toFp[Name](s[32 .. ^1]) + result.x = x + result.y = y + echo result.toHex() + if not bool(result.isNeutral()): + doAssert isOnCurve(result.x, result.y, G1).bool, "Input point is not on curve!" + +proc toFp2*[Name: static Algebra](x: seq[byte]): Fp2[Name] = + let c0 = toFp[Name](x[0 .. 31]) + let c1 = toFp[Name](x[32 .. 63]) + result.c0 = c0 + result.c1 = c1 + +proc toEcG2*[Name: static Algebra](s: seq[byte]): EC_ShortW_Aff[Fp2[Name], G2] = + let x = toFp2[Name](s[0 .. 63]) + let y = toFp2[Name](s[64 .. ^1]) + result.x = x + result.y = y + if not bool(result.isNeutral()): + doAssert isOnCurve(result.x, result.y, G2).bool, "Input point is not on curve!" + +## Currently not used +proc randomFieldElement*[Name: static Algebra](): Fp[Name] = + ## random element in ~Fp[T]~ + let m = Fp[Name].getModulus() + var b: matchingBigInt(Name) + + while b.isZero().bool or (b > m).bool: + assert b.limbs.sysrand() + result.fromBig(b) diff --git a/constantine/proof_systems/constraint_systems/manual_groth16.nim b/constantine/proof_systems/constraint_systems/manual_groth16.nim new file mode 100644 index 000000000..ed40256d8 --- /dev/null +++ b/constantine/proof_systems/constraint_systems/manual_groth16.nim @@ -0,0 +1,369 @@ +import ./r1cs_circom_parser, + ./zkey_binary_parser, + ./wtns_binary_parser + +import ../../math/[arithmetic, extension_fields] +import ../../math/io/[io_bigints, io_fields, io_ec, io_extfields] +import ../../platforms/abstractions +import ../../named/[algebras, properties_fields, properties_curves] +import ../../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime] +import ../../named/zoo_generators +import ../../csprngs/sysrand + +import ../../math/polynomials/[fft_fields, fft_lut] + +from std / math import log2 + +import ./groth16_utils + +type + Groth16Prover[Name: static Algebra] = object + ## XXX: In the future the below should be typed objects that are already unmarshalled! + zkey: ZkeyBin + wtns: WtnsBin + r1cs: R1CS + # secret random values `r`, `s` for the proof + r: Fr[Name] + s: Fr[Name] + +proc randomFieldElement[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] = + ## random element in ~Fp[Name]~ + let m = Fr[Name].getModulus() + var b: matchingOrderBigInt(Name) + + while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? + assert b.limbs.sysrand() + result.fromBig(b) + +proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: ZkeyBin, wtns: WtnsBin, r1cs: R1CS): Groth16Prover[Name] = + result = Groth16Prover[Name]( + zkey: zkey, + wtns: wtns, + r1cs: r1cs, + r: randomFieldElement(Fr[Name]), ## XXX: do we want to do this in `init`? + s: randomFieldElement(Fr[Name]) + ) + +proc getWitnesses[Name: static Algebra](ctx: Groth16Prover[Name]): seq[Fr[Name]] = + let witnesses = ctx.wtns.witnesses() + result = newSeq[Fr[Name]](witnesses.len) + for i, w in witnesses: + result[i] = toFr[Name](w.data, isMont = false) ## Improtant: Witness does *not* store numbers in Montgomery rep + +proc asEC[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp[Name]]): seq[EC_ShortW_Aff[Fp[Name], G1]] = + result = newSeq[EC_ShortW_Aff[Fp[Name], G1]](pts.len) + for i, el in pts: + result[i] = toEcG1[Name](el) + +proc asEC2[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp2[Name]]): seq[EC_ShortW_Aff[Fp2[Name], G2]] = + result = newSeq[EC_ShortW_Aff[Fp2[Name], G2]](pts.len) + for i, el in pts: + result[i] = toEcG2[Name](el) + + +proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = + # A_p is defined as + # A_p = α_1 + (Σ_i [W]_i · A_i) + [r] · δ_1 + # A_p = alpha1 + sum(A[i] * witness[i] for i in range(zkey.g16h.nVars)) + r * delta1 + # where of course in principle `α_1` is `g_1^{α}` etc. + let g16h = ctx.zkey.groth16Header() + + let alpha1 = g16h.alpha1.toEcG1[:Name]() + let delta1 = g16h.delta1.toEcG1[:Name]() + + # Declare `A_p` for the result + var A_p: EC_ShortW_Jac[Fp[Name], G1] + + # Compute the terms independent of the witnesses + A_p = alpha1.getJacobian + ctx.r * delta1 + echo A_p.toHex() + + let As = ctx.zkey.Afield().points.asEC(Fp[Name]) + doAssert As.len == wt.len + for i in 0 ..< As.len: + A_p += wt[i] * As[i] + + # Via MSM + var A_p_msm: EC_ShortW_Jac[Fp[Name], G1] + A_p_msm.multiScalarMul_vartime(wt, As) + A_p_msm += alpha1.getJacobian + ctx.r * delta1 + + doAssert (A_p == A_p_msm).bool + + result = A_p + +proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp2[Name], G2] = + # B_p = beta2 + sum(B2[i] * witness[i] for i in range(zkey.g16h.nVars)) + s * delta2 + # B_p = β_2 + (Σ_i [W]_i · B2_i) + [s] · δ_2 + # where of course in principle `β_1` is `g_1^{β}` etc. + let g16h = ctx.zkey.groth16Header() + + let beta2 = g16h.beta2.toEcG2[:Name]() + let delta2 = g16h.delta2.toEcG2[:Name]() + + # Declare `B_p` for the result + var B_p: EC_ShortW_Jac[Fp2[Name], G2] + + # Compute the terms independent of the witnesses + B_p = beta2.getJacobian + ctx.s * delta2 + + let Bs = ctx.zkey.B2field().points.asEC2(Fp2[Name]) + + doAssert Bs.len == wt.len + # could compute via MSM + for i in 0 ..< Bs.len: + B_p += wt[i] * Bs[i] + + result = B_p + +proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = + let g16h = ctx.zkey.groth16Header() + + let beta1 = g16h.beta1.toEcG1[:Name]() + let delta1 = g16h.delta1.toEcG1[:Name]() + result = beta1.getJacobian + ctx.s * delta1 + + # Get the B1 data + let Bs = ctx.zkey.B1field().points.asEC(Fp[Name]) + + doAssert Bs.len == wt.len + for i in 0 ..< Bs.len: + result += wt[i] * Bs[i] + +proc buildABC[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): tuple[A, B, C: seq[Fr[Name]]] = + # Extract required data using accessors + let + coeffs = ctx.zkey.coeffs() + g16h = ctx.zkey.groth16Header() + domainSize = g16h.domainSize + nCoeff = coeffs.num + + # Initialize output sequences + var + outBuffA = newSeq[Fr[Name]](domainSize) + outBuffB = newSeq[Fr[Name]](domainSize) + outBuffC = newSeq[Fr[Name]](domainSize) + + template toPUA[Name](x: seq[Name]): untyped = cast[ptr UncheckedArray[Name]](addr x[0]) + + var outBuf = [toPUA outBuffA, toPUA outBuffB] + + # Build A and B polynomials + for i in 0 ..< nCoeff: + let + m = coeffs.cs[i].matrix + c = coeffs.cs[i].section + s = coeffs.cs[i].index + coef = toFr[Name](coeffs.cs[i].value, true, false) + assert s.int < wt.len + outBuf[m][c] = outBuf[m][c] + coef * wt[s] + + # Compute C polynomial + for i in 0 ..< domainSize: + ## XXX: Here this product yields numbers in SnarkJS I cannot reproduce + outBuffC[i].prod(outBuffA[i], outBuffB[i]) + + result = (outBuffA, outBuffB, outBuffC) + +proc transform[Name: static Algebra](args: seq[Fr[Name]], inc: Fr[Name]): seq[Fr[Name]] = + ## Applies (multiplies) increasing powers of `inc` to each element + ## of `args`, i.e. + ## + ## `{ a[0], a[1]·inc, a[2]·inc², a[3]·inc³, ... }`. + ## + ## In our case `inc` is usually a root of unity of the power given by + ## `log2( FFT order ) + 1`. + result = newSeq[Fr[Name]](args.len) + var cur = Fr[Name].fromUint(1.uint64) + for i in 0 ..< args.len: + result[i] = args[i] * cur + cur *= inc + +proc itf[Name: static Algebra](arg: seq[Fr[Name]]): seq[Fr[Name]] = + ## inverse FFT -> transform -> forward FFT + ## + ## Equivalent to SnarkJS (same for A and C): + ## ```js + ## const buffB = await Fr.ifft(buffB_T, "", "", logger, "IFFT_B"); + ## const buffBodd = await Fr.batchApplyKey(buffB, Fr.e(1), inc); + ## const buffBodd_T = await Fr.fft(buffBodd, "", "", logger, "FFT_B"); + ## ``` + let buffA = ifft_vartime(arg) + + let power = log2(arg.len.float).int # arg is power of 2 + let inc = scaleToRootOfUnity(Name)[power + 1] + let buffAodd = buffA.transform(inc) + + result = fft_vartime(buffAodd) + +proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW_Jac[Fp[Name], G1], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = + # # Compute C_p + # C_p = sum(C[i] * witness[i] for i in range(zkey.g16h.nVars)) + # C_p += A_p * s + r * B_p - r * s * delta1 + # C_p += sum(H[i] * (witness[i] * witness[j]) for i, j in r1cs.constraints) + + let abc = buildABC[Name](ctx, wt) + + let fftD = FFTDescriptor[Fr[Name]].init(abc[0].len * 2) + + let A = itf(abc[0]) + let B = itf(abc[1]) + let C = itf(abc[2]) + + # combine A, B, C again + var jabc = newSeq[Fr[Name]](A.len) + for i in 0 ..< jabc.len: + jabc[i] = A[i] * B[i] - C[i] + + # Get the C data + let Cs = ctx.zkey.Cfield().points.asEC(Fp[Name]) + # Get private witnesses + let g16h = ctx.zkey.groth16Header() + ## XXX: Why is `nPublic` `1` when `Cs.len` ends up as `4` and `nVars` is `6`? + echo "LEN ? ", Cs.len, " total witnesses? ", wt.len, " public? ", g16h.nPublic, " total? ", g16h.nVars + + var priv = newSeqOfCap[Fr[Name]](wt.len) + let nPub = g16h.nVars.int - Cs.len # g16h.nPublic.int + for i in nPub ..< g16h.nVars.int: + priv.add wt[i] + + doAssert Cs.len == priv.len, " Cs: " & $Cs.len & ", priv: " & $priv.len + var cw: EC_ShortW_Jac[Fp[Name], G1] + for i in 0 ..< Cs.len: + cw += priv[i] * Cs[i] + + let Hs = ctx.zkey.Hfield().points.asEC(Fp[Name]) + + doAssert Hs.len == jabc.len + var resH: EC_ShortW_Jac[Fp[Name], G1] + for i in 0 ..< Hs.len: + resH += jabc[i] * Hs[i] + + let delta1 = g16h.delta1.toEcG1[:Name]() + + # Declare `C_p` for the result + var C_p: EC_ShortW_Jac[Fp[Name], G1] + C_p = ctx.s * A_p + ctx.r * B1_p - (ctx.r * ctx.s) * delta1 + cw + resH + result = C_p + +proc prove[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Jac[Fp[Name], G1], + B: EC_ShortW_Jac[Fp2[Name], G2], + C: EC_ShortW_Jac[Fp[Name], G1]] = + #[ + XXX: fix up notation here! + r = random_scalar_field_element() + s = random_scalar_field_element() + + # Compute A_p + A_p = alpha1 + sum(A[i] * witness[i] for i in range(zkey.g16h.nVars)) + r * delta1 + + # Compute B_p + B_p = beta2 + sum(B2[i] * witness[i] for i in range(zkey.g16h.nVars)) + s * delta2 + + # Compute C_p + C_p = sum(C[i] * witness[i] for i in range(zkey.g16h.nVars)) + C_p += A_p * s + r * B_p - r * s * delta1 + C_p += sum(H[i] * (witness[i] * witness[j]) for i, j in r1cs.constraints) + + proof = (A_p, B_p, C_p) + ]# + + let wt = ctx.getWitnesses() + + let A_p = ctx.calcAp(wt) + let B2_p = ctx.calcBp(wt) + let B1_p = ctx.calcB1(wt) + let C_p = ctx.calcCp(A_p, B1_p, wt) + + result = (A: A_p, B: B2_p, C: C_p) + +when isMainModule: + + let wtns = parseWtnsFile("/home/basti/org/constantine/moonmath/circom/three_fac_js/witness.wtns") + let zkey = parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") + let r1cs = parseR1csFile("/home/basti/org/constantine/moonmath/circom/three_fac.r1cs") + .toR1CS + + const T = BN254_Snarks + let g16h = zkey.groth16Header() + ## NOTE: We *expect* all these to be 0, because they are the respective moduli for + ## `Fp` and `Fr`! + ## XXX: move to a test case + echo "q = ", toFp[T](g16h.q, false).toDecimal() + echo "r = ", toFr[T](g16h.r, false).toDecimal() + echo "wtns r = ", toFr[T](wtns.header.r, false).toDecimal() + + var ctx = Groth16Prover[T].init(zkey, wtns, r1cs) + + ## Note: We will now calculate the proof using a fixed, non secret set of points + ## r, s (or r, t) ∈ 𝔽r in order to compare with a calculation of `snarkjs`. We + ## hacked in a print of the secret it randomly sampled. + # The 'secret' constants from + ## XXX: Move to a test case! + const rSJ = @[ + byte 143, 55, 118, 73, 42, 115, 60, 77, + 95, 209, 41, 144, 250, 137, 138, 71, + 176, 242, 186, 232, 179, 30, 88, 255, + 198, 161, 182, 150, 220, 149, 33, 19 + ] + const sSJ = @[ + byte 213, 105, 105, 27, 129, 249, 139, 158, + 221, 68, 37, 163, 59, 71, 19, 108, + 60, 153, 183, 156, 25, 148, 37, 9, + 85, 205, 250, 246, 132, 142, 244, 36 + ] + + # construct the random element `r` from snarkjs "secret" r + let r = toFr[BN254_Snarks](rSJ) + # and `s` + let s = toFr[BN254_Snarks](sSJ) + + ctx.r = r + ctx.s = s + + let (A_p, B2_p, C_p) = ctx.prove() + + echo "\n==============================\n" + echo "A_p#16 = ", A_p.toHex() + echo "A_p#10 = ", A_p.toDecimal() + echo "------------------------------" + echo "B_p#16 = ", B2_p.toHex() + echo "B_p#10 = ", B2_p.toDecimal() + echo "------------------------------" + echo "C_p#16 = ", C_p.toHex() + echo "C_p#10 = ", C_p.toDecimal() + + ## SnarkJS yields: + ## + ## `snarkjs groth16 prove three_fac_final.zkey ../../circom/three_fac_js/witness.wtns proof.json public.json` + ## + #[ +{ + "pi_a": [ + "5525629793372463776337933283524928112323589665400780041477380790923758613749", + "21229177076048503863699135039723099340209138028149442778064006577287317302601", + "1" + ], + "pi_b": [ + [ + "10113559933709853115219982658131344715329670532374721861173670433756614595086", + "748111067660143353202076805159132563350177510079329482395824347599610874338" + ], + [ + "14193926223452546125681093394065339196897041249946578591171606543100010486627", + "871256420758854731396810855688710623510558493821614150596755347032202324148" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "18517653609733492682442099361591955563405567929398531111532682405176646276349", + "17315036348446251361273519572420522936369550153340386126725970444173389652255", + "1" + ], + "protocol": "groth16", + "curve": "bn128" +} + ]# From ff4ea0f22aef6c412f9016ab5da6769ec3417ef6 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 13:28:53 +0200 Subject: [PATCH 03/49] minor comments, cleanup, TODOs --- .../constraint_systems/r1cs_circom_parser.nim | 1 + .../constraint_systems/wtns_binary_parser.nim | 8 ++++---- .../constraint_systems/zkey_binary_parser.nim | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim index a2254b932..ec6e6a053 100644 --- a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim +++ b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim @@ -121,6 +121,7 @@ type R1csCustomGatesList* = object R1csCustomGatesApp* = object + ## XXX: Make this a `R1CS[T]` which takes care of parsing the field elements R1CS* = object magic*: array[4, char] version*: uint32 diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index 0a349b5ef..1d3e6bd46 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -40,10 +40,7 @@ import - Content: Each witness element is a 256-bit (32 bytes) unsigned integer in Big Endian format ]# -# We use `sortedByIt` to sort the different sections in the file by their -# `WtnsSectionKind` from std / sequtils import filterIt -from std / algorithm import sortedByIt from std / strutils import endsWith type @@ -58,7 +55,7 @@ type num*: uint32 # number of witness elements Witness* = object - data*: seq[byte] + data*: seq[byte] ## Important: The values are *not* Montgomery encoded Section* = object size*: uint64 # NOTE: in the real file the section type is *FIRST* and then the size @@ -82,6 +79,9 @@ type # of each different section in the file and then parse them in increasing # order of the section types +## XXX: Add `Wtns[T]` type, which takes care of converting field elements and +## does not contain `seq[Section]` anymore + func header*(wtns: WtnsBin): WitnessHeader = result = wtns.sections.filterIt(it.sectionType == kHeader)[0].header diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index 169159d92..adcb6a77e 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -108,6 +108,9 @@ type # of each different section in the file and then parse them in increasing # order of the section types +## XXX: Add `Zkey[T]` type, which takes care of converting field elements and +## does not contain `seq[Section]` anymore + func header*(zkey: ZkeyBin): Header = result = zkey.sections.filterIt(it.sectionType == kHeader)[0].header From fd856bbc7e55eaf73be0a7145e460220a3800333 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 17:51:43 +0200 Subject: [PATCH 04/49] add note about typed R1CS --- .../proof_systems/constraint_systems/r1cs_circom_parser.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim index ec6e6a053..80f7aa7d8 100644 --- a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim +++ b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim @@ -122,6 +122,7 @@ type R1csCustomGatesApp* = object ## XXX: Make this a `R1CS[T]` which takes care of parsing the field elements + ## NOTE: For the time being we don't actually use the parsed data R1CS* = object magic*: array[4, char] version*: uint32 From 15965b24350b823ffc3055888e4d623353a5564b Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 17:52:25 +0200 Subject: [PATCH 05/49] add 'typed' variant of Zkey --- .../constraint_systems/zkey_binary_parser.nim | 182 ++++++++++++++---- 1 file changed, 144 insertions(+), 38 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index adcb6a77e..4894c5c91 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -10,12 +10,13 @@ # import ../../serialization/[io_limbs, parsing], - constantine/platforms/[fileio, abstractions] + constantine/platforms/[fileio, abstractions], + ../../named/algebras, # Fr, Fp + ../../math/extension_fields, # Fp2 + ../../math/elliptic/[ec_shortweierstrass_affine], # EC types + ./groth16_utils # to unmarshal data -# We use `sortedByIt` to sort the different sections in the file by their -# `ZkeySectionKind` from std / sequtils import filterIt -from std / algorithm import sortedByIt from std / strutils import endsWith type @@ -35,7 +36,7 @@ type Header* = object proverType*: uint32 ## Must be `1` for Groth16 - Groth16Header* = object + Groth16Header_b* = object n8q*: uint32 # Size of base field in bytes (4 bytes, unsigned integer) q*: seq[byte] # Prime of the base field (n8q bytes) n8r*: uint32 # Size of scalar field in bytes (4 bytes, unsigned integer) @@ -50,28 +51,61 @@ type delta1*: seq[byte] # delta in G1 (2 * n8q bytes) delta2*: seq[byte] # delta in G2 (4 * n8q bytes) + Groth16Header*[Name: static Algebra] = object + n8q*: uint32 # Size of base field in bytes (4 bytes, unsigned integer) + q*: seq[byte] # Prime of the base field (n8q bytes) + n8r*: uint32 # Size of scalar field in bytes (4 bytes, unsigned integer) + r*: seq[byte] # Prime of the scalar field (n8r bytes) + nVars*: uint32 # Total number of variables (4 bytes, unsigned integer) + nPublic*: uint32 # Number of public variables (4 bytes, unsigned integer) + domainSize*: uint32 # Size of the domain (4 bytes, unsigned integer) + alpha1*: EC_ShortW_Aff[Fp[Name], G1] + beta1*: EC_ShortW_Aff[Fp[Name], G1] + beta2*: EC_ShortW_Aff[Fp2[Name], G2] + gamma2*: EC_ShortW_Aff[Fp2[Name], G2] + delta1*: EC_ShortW_Aff[Fp[Name], G1] + delta2*: EC_ShortW_Aff[Fp2[Name], G2] ## Generic section containing multiple points on one of the curves. Will be unmarshaled into ## points on the correct curve afterwars. DataSection* = object points*: seq[seq[byte]] - IC* = DataSection - A* = DataSection - B1* = DataSection - B2* = DataSection - C* = DataSection - H* = DataSection - - Coefficient* = object + # `_b` suffix for raw binary + ## NOTE: Maybe a `Raw` suffix would be better + IC_b* = DataSection + A_b* = DataSection + B1_b* = DataSection + B2_b* = DataSection + C_b* = DataSection + H_b* = DataSection + + IC*[Name : static Algebra] = seq[EC_ShortW_Aff[Fp[Name], G1]] + A*[Name : static Algebra] = seq[EC_ShortW_Aff[Fp[Name], G1]] + B1*[Name : static Algebra] = seq[EC_ShortW_Aff[Fp[Name], G1]] + B2*[Name : static Algebra] = seq[EC_ShortW_Aff[Fp2[Name], G2]] + C*[Name : static Algebra] = seq[EC_ShortW_Aff[Fp[Name], G1]] + H*[Name : static Algebra] = seq[EC_ShortW_Aff[Fp[Name], G1]] + + Coefficient_b* = object matrix*: uint32 section*: uint32 index*: uint32 value*: seq[byte] # n8r bytes - Coefficients* = object + Coefficients_b* = object num*: uint32 # number of coefficients - cs*: seq[Coefficient] + cs*: seq[Coefficient_b] + + Coefficient*[Name: static Algebra] = object + matrix*: uint32 + section*: uint32 + index*: uint32 + value*: Fr[Name] + + Coefficients*[Name: static Algebra] = object + num*: uint32 # number of coefficients + cs*: seq[Coefficient[Name]] Contributions* = object hash*: array[64, byte] # hash of the circuit @@ -85,17 +119,17 @@ type case sectionType*: ZkeySectionKind of kInvalid: discard of kHeader: header*: Header - of kGroth16Header: g16h: Groth16Header - of kIC: ic: IC - of kCoeffs: coeffs: Coefficients - of kA: a: A - of kB1: b1: B1 - of kB2: b2: B2 - of kC: c: C - of kH: h: H + of kGroth16Header: g16h: Groth16Header_b + of kIC: ic: IC_b + of kCoeffs: coeffs: Coefficients_b + of kA: a: A_b + of kB1: b1: B1_b + of kB2: b2: B2_b + of kC: c: C_b + of kH: h: H_b of kContributions: contr: Contributions - ## `ZkeyBin` is binary compatible with an R1CS binary file. Meaning it follows the structure + ## `ZkeyBin` is binary compatible with a `.zkey` binary file. Meaning it follows the structure ## of the file (almost) exactly. The only difference is in the section header. The size comes ## *after* the kind, which we don't reproduce in `Section` above ZkeyBin* = object @@ -108,33 +142,102 @@ type # of each different section in the file and then parse them in increasing # order of the section types -## XXX: Add `Zkey[T]` type, which takes care of converting field elements and -## does not contain `seq[Section]` anymore + ## `Zkey` is a "typed" version of the binary file, where all field elements have already been + ## unmarshalled according to the encoding spec used by SnarkJS. + Zkey*[Name: static Algebra] = object + version*: uint32 + header*: Header + g16h*: Groth16Header[Name] + ic*: IC[Name] + coeffs*: Coefficients[Name] + A*: A[Name] + B1*: B1[Name] + B2*: B2[Name] + C*: C[Name] + H*: H[Name] + contr*: Contributions func header*(zkey: ZkeyBin): Header = result = zkey.sections.filterIt(it.sectionType == kHeader)[0].header -func Afield*(zkey: ZkeyBin): A = +func Afield*(zkey: ZkeyBin): A_b = result = zkey.sections.filterIt(it.sectionType == kA)[0].a -func B1field*(zkey: ZkeyBin): B1 = +func B1field*(zkey: ZkeyBin): B1_b = result = zkey.sections.filterIt(it.sectionType == kB1)[0].b1 -func B2field*(zkey: ZkeyBin): B2 = +func B2field*(zkey: ZkeyBin): B2_b = result = zkey.sections.filterIt(it.sectionType == kB2)[0].b2 -func Cfield*(zkey: ZkeyBin): C = +func Cfield*(zkey: ZkeyBin): C_b = result = zkey.sections.filterIt(it.sectionType == kC)[0].c -func Hfield*(zkey: ZkeyBin): H = +func Hfield*(zkey: ZkeyBin): H_b = result = zkey.sections.filterIt(it.sectionType == kH)[0].h -func coeffs*(zkey: ZkeyBin): Coefficients = +func coeffs*(zkey: ZkeyBin): Coefficients_b = result = zkey.sections.filterIt(it.sectionType == kCoeffs)[0].coeffs -func groth16Header*(zkey: ZkeyBin): Groth16Header = +func groth16Header*(zkey: ZkeyBin): Groth16Header_b = result = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h +func icField*(zkey: ZkeyBin): IC_b = + result = zkey.sections.filterIt(it.sectionType == kIC)[0].ic + +func contributions*(zkey: ZkeyBin): Contributions = + result = zkey.sections.filterIt(it.sectionType == kContributions)[0].contr + +func to*[Name: static Algebra](coefs: Coefficients_b, _: typedesc[Coefficients[Name]]): Coefficients[Name] = + result = Coefficients[Name](num: coefs.num, + cs: newSeq[Coefficient[Name]](coefs.num)) + for i in 0 ..< coefs.num: + let + m = coefs.cs[i].matrix + c = coefs.cs[i].section + s = coefs.cs[i].index + result.cs[i] = Coefficient[Name]( + matrix: m, section: c, index: s, + value: toFr[Name](coefs.cs[i].value, true) + ) + +proc to*[Name: static Algebra](g16h: Groth16Header_b, _: typedesc[Groth16Header[Name]]): Groth16Header[Name] = + let alpha1 = g16h.alpha1.toEcG1[:Name]() + let beta1 = g16h.beta1.toEcG1[:Name]() + let beta2 = g16h.beta2.toEcG2[:Name]() + let gamma2 = g16h.gamma2.toEcG2[:Name]() + let delta1 = g16h.delta1.toEcG1[:Name]() + let delta2 = g16h.delta2.toEcG2[:Name]() + + result = Groth16Header[Name]( + n8q: g16h.n8q, + q: g16h.q, + n8r: g16h.n8r, + r: g16h.r, + nVars: g16h.nVars, + nPublic: g16h.nPublic, + domainSize: g16h.domainSize, + alpha1: alpha1, + beta1: beta1, + beta2: beta2, + gamma2: gamma2, + delta1: delta1, + delta2: delta2 + ) + +proc toZkey*[Name: static Algebra](zkey: ZkeyBin): Zkey[Name] = + result = Zkey[Name]( + version: zkey.version, + header: zkey.header(), + g16h: zkey.groth16header().to(Groth16Header[Name]), + ic: zkey.icField().points.asEC(Fp[Name]), + coeffs: zkey.coeffs().to(Coefficients[Name]), + A: zkey.AField().points.asEC(Fp[Name]), + B1: zkey.B1Field().points.asEC(Fp[Name]), + B2: zkey.B2Field().points.asEC2(Fp2[Name]), + C: zkey.CField().points.asEC(Fp[Name]), + H: zkey.HField().points.asEC(Fp[Name]), + contr: zkey.contributions() + ) proc initSection(kind: ZkeySectionKind, size: uint64): Section = result = Section(sectionType: kind, size: size) @@ -159,7 +262,7 @@ proc parseHeader(f: File, h: var Header): bool = doAssert h.proverType == 1, "Prover type must be `1` for Groth16, found: " & $h.proverType result = true # would have returned before due to `?` otherwise -proc parseGroth16Header(f: File, g16h: var Groth16Header): bool = +proc parseGroth16Header(f: File, g16h: var Groth16Header_b): bool = var buf: seq[byte] for field, v in fieldPairs(g16h): when typeof(v) is uint32: @@ -209,7 +312,7 @@ proc parseDatasection(f: File, s: var Section, kind: ZkeySectionKind, size: uint raiseAssert "Not a data section: " & $kind result = true -proc parseCoefficient(f: File, s: var Coefficient, size: uint64): bool = +proc parseCoefficient(f: File, s: var Coefficient_b, size: uint64): bool = ?f.parseInt(s.matrix, littleEndian) ?f.parseInt(s.section, littleEndian) ?f.parseInt(s.index, littleEndian) @@ -217,10 +320,10 @@ proc parseCoefficient(f: File, s: var Coefficient, size: uint64): bool = ?f.readInto(s.value) result = true -proc parseCoefficients(f: File, s: var Coefficients, zkey: ZkeyBin): bool = +proc parseCoefficients(f: File, s: var Coefficients_b, zkey: ZkeyBin): bool = ?f.parseInt(s.num, littleEndian) let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme - s.cs = newSeq[Coefficient](s.num) + s.cs = newSeq[Coefficient_b](s.num) for i in 0 ..< s.num: # parse coefficients echo "Parsing coefficient with : ", g16h.n8r, " bytes" ?f.parseCoefficient(s.cs[i], g16h.n8r) @@ -274,4 +377,7 @@ proc parseZkeyFile*(path: string): ZkeyBin = when isMainModule: #echo parseZkeyFile("/tmp/test.zkey") #echo parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac0000.zkey") - echo parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") + let zkey = parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") + echo zkey + + echo zkey.toZkey[:BN254_Snarks]() From 7954f7b39e1e6c0f37a10570ca8435001b06fe0a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 17:52:37 +0200 Subject: [PATCH 06/49] add 'typed' variant of Witness file data --- .../constraint_systems/wtns_binary_parser.nim | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index 1d3e6bd46..734909336 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -10,7 +10,9 @@ # import ../../serialization/[io_limbs, parsing], - constantine/platforms/[fileio, abstractions] + constantine/platforms/[fileio, abstractions], + ../../named/algebras, # Fr + ./groth16_utils #[ 1. File Header: @@ -79,6 +81,11 @@ type # of each different section in the file and then parse them in increasing # order of the section types + Wtns*[Name: static Algebra] = object + version*: uint32 + header*: WitnessHeader + witnesses*: seq[Fr[Name]] + ## XXX: Add `Wtns[T]` type, which takes care of converting field elements and ## does not contain `seq[Section]` anymore @@ -88,6 +95,18 @@ func header*(wtns: WtnsBin): WitnessHeader = func witnesses*(wtns: WtnsBin): seq[Witness] = result = wtns.sections.filterIt(it.sectionType == kData)[0].wtns +proc getWitnesses[Name: static Algebra](witnesses: seq[Witness]): seq[Fr[Name]] = + result = newSeq[Fr[Name]](witnesses.len) + for i, w in witnesses: + result[i] = toFr[Name](w.data, isMont = false) ## Improtant: Witness does *not* store numbers in Montgomery rep + +proc toWtns*[Name: static Algebra](wtns: WtnsBin): Wtns[Name] = + result = Wtns[Name]( + version: wtns.version, + header: wtns.header(), + witnesses: wtns.witnesses().getWitnesses[:Name]() + ) + proc initSection(kind: WtnsSectionKind, size: uint64): Section = result = Section(sectionType: kind, size: size) From 3ec132ffe8d29034c1401b19563a6733907882dc Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 17:52:58 +0200 Subject: [PATCH 07/49] move `asEC*`, `randomFieldElement` to utils --- .../constraint_systems/groth16_utils.nim | 20 ++++++++++++++----- .../constraint_systems/manual_groth16.nim | 11 ---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/groth16_utils.nim b/constantine/proof_systems/constraint_systems/groth16_utils.nim index 1a3c15c55..f6db40161 100644 --- a/constantine/proof_systems/constraint_systems/groth16_utils.nim +++ b/constantine/proof_systems/constraint_systems/groth16_utils.nim @@ -53,11 +53,21 @@ proc toEcG2*[Name: static Algebra](s: seq[byte]): EC_ShortW_Aff[Fp2[Name], G2] = doAssert isOnCurve(result.x, result.y, G2).bool, "Input point is not on curve!" ## Currently not used -proc randomFieldElement*[Name: static Algebra](): Fp[Name] = - ## random element in ~Fp[T]~ - let m = Fp[Name].getModulus() - var b: matchingBigInt(Name) +proc randomFieldElement*[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] = + ## random element in ~Fr[Name]~ + let m = Fr[Name].getModulus() + var b: matchingOrderBigInt(Name) - while b.isZero().bool or (b > m).bool: + while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? assert b.limbs.sysrand() result.fromBig(b) + +proc asEC*[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp[Name]]): seq[EC_ShortW_Aff[Fp[Name], G1]] = + result = newSeq[EC_ShortW_Aff[Fp[Name], G1]](pts.len) + for i, el in pts: + result[i] = toEcG1[Name](el) + +proc asEC2*[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp2[Name]]): seq[EC_ShortW_Aff[Fp2[Name], G2]] = + result = newSeq[EC_ShortW_Aff[Fp2[Name], G2]](pts.len) + for i, el in pts: + result[i] = toEcG2[Name](el) diff --git a/constantine/proof_systems/constraint_systems/manual_groth16.nim b/constantine/proof_systems/constraint_systems/manual_groth16.nim index ed40256d8..99e7a1a69 100644 --- a/constantine/proof_systems/constraint_systems/manual_groth16.nim +++ b/constantine/proof_systems/constraint_systems/manual_groth16.nim @@ -50,17 +50,6 @@ proc getWitnesses[Name: static Algebra](ctx: Groth16Prover[Name]): seq[Fr[Name]] for i, w in witnesses: result[i] = toFr[Name](w.data, isMont = false) ## Improtant: Witness does *not* store numbers in Montgomery rep -proc asEC[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp[Name]]): seq[EC_ShortW_Aff[Fp[Name], G1]] = - result = newSeq[EC_ShortW_Aff[Fp[Name], G1]](pts.len) - for i, el in pts: - result[i] = toEcG1[Name](el) - -proc asEC2[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp2[Name]]): seq[EC_ShortW_Aff[Fp2[Name], G2]] = - result = newSeq[EC_ShortW_Aff[Fp2[Name], G2]](pts.len) - for i, el in pts: - result[i] = toEcG2[Name](el) - - proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = # A_p is defined as # A_p = α_1 + (Σ_i [W]_i · A_i) + [r] · δ_1 From 9291badba6cea84b68713120b80f92827f4be944 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 13 Aug 2024 17:53:32 +0200 Subject: [PATCH 08/49] use 'typed' form of zkey / witnessss data objects --- .../constraint_systems/manual_groth16.nim | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/manual_groth16.nim b/constantine/proof_systems/constraint_systems/manual_groth16.nim index 99e7a1a69..5a4c6fdab 100644 --- a/constantine/proof_systems/constraint_systems/manual_groth16.nim +++ b/constantine/proof_systems/constraint_systems/manual_groth16.nim @@ -19,8 +19,8 @@ import ./groth16_utils type Groth16Prover[Name: static Algebra] = object ## XXX: In the future the below should be typed objects that are already unmarshalled! - zkey: ZkeyBin - wtns: WtnsBin + zkey: Zkey[Name] + wtns: Wtns[Name] r1cs: R1CS # secret random values `r`, `s` for the proof r: Fr[Name] @@ -35,7 +35,7 @@ proc randomFieldElement[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] = assert b.limbs.sysrand() result.fromBig(b) -proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: ZkeyBin, wtns: WtnsBin, r1cs: R1CS): Groth16Prover[Name] = +proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: Zkey[Name], wtns: Wtns[Name], r1cs: R1CS): Groth16Prover[Name] = result = Groth16Prover[Name]( zkey: zkey, wtns: wtns, @@ -44,21 +44,15 @@ proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: ZkeyBin s: randomFieldElement(Fr[Name]) ) -proc getWitnesses[Name: static Algebra](ctx: Groth16Prover[Name]): seq[Fr[Name]] = - let witnesses = ctx.wtns.witnesses() - result = newSeq[Fr[Name]](witnesses.len) - for i, w in witnesses: - result[i] = toFr[Name](w.data, isMont = false) ## Improtant: Witness does *not* store numbers in Montgomery rep - proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = # A_p is defined as # A_p = α_1 + (Σ_i [W]_i · A_i) + [r] · δ_1 # A_p = alpha1 + sum(A[i] * witness[i] for i in range(zkey.g16h.nVars)) + r * delta1 # where of course in principle `α_1` is `g_1^{α}` etc. - let g16h = ctx.zkey.groth16Header() + let g16h = ctx.zkey.g16h - let alpha1 = g16h.alpha1.toEcG1[:Name]() - let delta1 = g16h.delta1.toEcG1[:Name]() + let alpha1 = g16h.alpha1 + let delta1 = g16h.delta1 # Declare `A_p` for the result var A_p: EC_ShortW_Jac[Fp[Name], G1] @@ -67,7 +61,7 @@ proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): A_p = alpha1.getJacobian + ctx.r * delta1 echo A_p.toHex() - let As = ctx.zkey.Afield().points.asEC(Fp[Name]) + let As = ctx.zkey.A doAssert As.len == wt.len for i in 0 ..< As.len: A_p += wt[i] * As[i] @@ -85,10 +79,10 @@ proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): # B_p = beta2 + sum(B2[i] * witness[i] for i in range(zkey.g16h.nVars)) + s * delta2 # B_p = β_2 + (Σ_i [W]_i · B2_i) + [s] · δ_2 # where of course in principle `β_1` is `g_1^{β}` etc. - let g16h = ctx.zkey.groth16Header() + let g16h = ctx.zkey.g16h - let beta2 = g16h.beta2.toEcG2[:Name]() - let delta2 = g16h.delta2.toEcG2[:Name]() + let beta2 = g16h.beta2 + let delta2 = g16h.delta2 # Declare `B_p` for the result var B_p: EC_ShortW_Jac[Fp2[Name], G2] @@ -96,7 +90,7 @@ proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): # Compute the terms independent of the witnesses B_p = beta2.getJacobian + ctx.s * delta2 - let Bs = ctx.zkey.B2field().points.asEC2(Fp2[Name]) + let Bs = ctx.zkey.B2 doAssert Bs.len == wt.len # could compute via MSM @@ -106,14 +100,14 @@ proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): result = B_p proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = - let g16h = ctx.zkey.groth16Header() + let g16h = ctx.zkey.g16h - let beta1 = g16h.beta1.toEcG1[:Name]() - let delta1 = g16h.delta1.toEcG1[:Name]() + let beta1 = g16h.beta1 + let delta1 = g16h.delta1 result = beta1.getJacobian + ctx.s * delta1 # Get the B1 data - let Bs = ctx.zkey.B1field().points.asEC(Fp[Name]) + let Bs = ctx.zkey.B1 doAssert Bs.len == wt.len for i in 0 ..< Bs.len: @@ -122,8 +116,8 @@ proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): proc buildABC[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): tuple[A, B, C: seq[Fr[Name]]] = # Extract required data using accessors let - coeffs = ctx.zkey.coeffs() - g16h = ctx.zkey.groth16Header() + coeffs = ctx.zkey.coeffs + g16h = ctx.zkey.g16h domainSize = g16h.domainSize nCoeff = coeffs.num @@ -143,7 +137,7 @@ proc buildABC[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]) m = coeffs.cs[i].matrix c = coeffs.cs[i].section s = coeffs.cs[i].index - coef = toFr[Name](coeffs.cs[i].value, true, false) + coef = coeffs.cs[i].value assert s.int < wt.len outBuf[m][c] = outBuf[m][c] + coef * wt[s] @@ -205,9 +199,9 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW jabc[i] = A[i] * B[i] - C[i] # Get the C data - let Cs = ctx.zkey.Cfield().points.asEC(Fp[Name]) + let Cs = ctx.zkey.C # Get private witnesses - let g16h = ctx.zkey.groth16Header() + let g16h = ctx.zkey.g16h ## XXX: Why is `nPublic` `1` when `Cs.len` ends up as `4` and `nVars` is `6`? echo "LEN ? ", Cs.len, " total witnesses? ", wt.len, " public? ", g16h.nPublic, " total? ", g16h.nVars @@ -221,14 +215,14 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW for i in 0 ..< Cs.len: cw += priv[i] * Cs[i] - let Hs = ctx.zkey.Hfield().points.asEC(Fp[Name]) + let Hs = ctx.zkey.H doAssert Hs.len == jabc.len var resH: EC_ShortW_Jac[Fp[Name], G1] for i in 0 ..< Hs.len: resH += jabc[i] * Hs[i] - let delta1 = g16h.delta1.toEcG1[:Name]() + let delta1 = g16h.delta1 # Declare `C_p` for the result var C_p: EC_ShortW_Jac[Fp[Name], G1] @@ -257,7 +251,7 @@ proc prove[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_J proof = (A_p, B_p, C_p) ]# - let wt = ctx.getWitnesses() + let wt = ctx.wtns.witnesses let A_p = ctx.calcAp(wt) let B2_p = ctx.calcBp(wt) @@ -268,13 +262,17 @@ proc prove[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_J when isMainModule: + const T = BN254_Snarks + let wtns = parseWtnsFile("/home/basti/org/constantine/moonmath/circom/three_fac_js/witness.wtns") + .toWtns[:T]() let zkey = parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") + .toZkey[:T]() let r1cs = parseR1csFile("/home/basti/org/constantine/moonmath/circom/three_fac.r1cs") .toR1CS - const T = BN254_Snarks - let g16h = zkey.groth16Header() + + let g16h = zkey.g16h ## NOTE: We *expect* all these to be 0, because they are the respective moduli for ## `Fp` and `Fr`! ## XXX: move to a test case From cb48a093d3a7670858045151050a49e49ffd9f3e Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 17:07:30 +0200 Subject: [PATCH 09/49] [tests] add test file for finite field FFT --- tests/math_polynomials/t_finite_field_fft.nim | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/math_polynomials/t_finite_field_fft.nim diff --git a/tests/math_polynomials/t_finite_field_fft.nim b/tests/math_polynomials/t_finite_field_fft.nim new file mode 100644 index 000000000..58a10e008 --- /dev/null +++ b/tests/math_polynomials/t_finite_field_fft.nim @@ -0,0 +1,100 @@ +import + unittest, + constantine/math/arithmetic, + constantine/math/io/[io_fields, io_bigints], + constantine/named/[algebras, properties_fields, properties_curves], + constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], + constantine/math/polynomials/[fft_fields, fft] + +## NOTE: This test must be compiled with `-d:CTT_TEST_CURVES` + +suite "FFT Tests (finite fields)": + test "FFT over Finite Field Fp[Fake13]": + + type F = Fp[Fake13] + + # Fr[Fake13] is GF(13) + let order = 4 # We'll use a small order for the test + # `5` is a generator for the order `4`, because `5^4 mod 13 = 1`: + # `5^0 mod 13 = 1 ` + # `5^1 mod 13 = 5 ` + # `5^2 mod 13 = 12` + # `5^3 mod 13 = 8 ` + # `5^4 mod 13 = 1 ` + let Gen = F.fromUInt(5'u64) + var fftDesc = FFTDescriptor[F].init(order, Gen) + defer: fftDesc.delete() + + # Input values + # Describes Polynomial: + # P(x) = a₀ + a₁·x + a₂·x² + a₃·x³ + # P(x) = 1 + 2·x + 3·x² + 4·x³ + var input = @[F.fromInt(1), F.fromInt(2), F.fromInt(3), F.fromInt(4)] + var output = newSeq[F](order) + + # Perform forward FFT + # The forward FFT returns the evaluation of polynomial P(x) at the + # roots of unity based on `Gen` + let status = fftDesc.fft_vartime(output, input) + check (status == FFTS_Success).bool + + # Expected output (calculated manually or with a known-good implementation) + let expected = @[ + # let `g(i) = 5^i mod 13` # where `5` is the generator, i.e. `5^4 mod 13 = 1` + F.fromUInt(10'u64), # P(g(0)) = (1 + 2·1 + 3·1² + 4·1³ ) mod 13 = 10 + F.fromUInt(1'u64), # P(g(1)) = (1 + 2·5 + 3·5² + 4·5³ ) mod 13 = 1 + F.fromUInt(11'u64), # P(g(2)) = (1 + 2·12 + 3·12² + 4·12³) mod 13 = 11 + F.fromUInt(8'u64) # P(g(3)) = (1 + 2·8 + 3·8² + 4·8³ ) mod 13 = 8 + ] + + for i in 0 ..< output.len: + check (output[i] == expected[i]).bool + + # Perform inverse FFT + var inverse_output = newSeq[F](order) + let inverse_status = fftDesc.ifft_vartime(inverse_output, output) + check (inverse_status == FFTS_Success).bool + + # Check if we get back the original input + for i in 0 ..< order: + check (inverse_output[i] == input[i]).bool + + test "FFT over finite field Fr[BN254_Snarks]": + type F = Fr[BN254_Snarks] + + const order = 4 + var fftDesc = FFTDescriptor[F].init(order) + defer: fftDesc.delete() + + # Input values + # Describes Polynomial: + # P(x) = a₀ + a₁·x + a₂·x² + a₃·x³ + # P(x) = 1 + 2·x + 3·x² + 4·x³ + var input = @[F.fromInt(1), F.fromInt(2), F.fromInt(3), F.fromInt(4)] + var output = newSeq[F](order) + + # Perform forward FFT + # The forward FFT returns the evaluation of polynomial P(x) at the + # roots of unity based on `Gen` + let status = fftDesc.fft_vartime(output, input) + check (status == FFTS_Success).bool + + template toFr(s: string): untyped = F.fromBig(matchingOrderBigInt(BN254_Snarks).fromHex(s, bigEndian)) + let expected = @[ + toFr "0x000000000000000000000000000000000000000000000000000000000000000a", + toFr "0x00000000000000016789af3a83522eb1969386a2f88c094a419fe246c11f9394", + toFr "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff", + toFr "0x30644e72e131a02850c6967bfe2f29ab91a061a5812d67470242134d2ee06c69" + ] + + for i in 0 ..< output.len: + check (output[i] == expected[i]).bool + + # Perform inverse FFT + var inverse_output = newSeq[F](order) + let inverse_status = fftDesc.ifft_vartime(inverse_output, output) + check (inverse_status == FFTS_Success).bool + + # Check if we get back the original input + for i in 0 ..< order: + check (inverse_output[i] == input[i]).bool From f774ece1ac9ce39837df6ed6afdbcaf6b5d9d87c Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 17:07:54 +0200 Subject: [PATCH 10/49] [groth16] whitespace --- .../proof_systems/constraint_systems/manual_groth16.nim | 4 ---- 1 file changed, 4 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/manual_groth16.nim b/constantine/proof_systems/constraint_systems/manual_groth16.nim index 5a4c6fdab..c59004bb4 100644 --- a/constantine/proof_systems/constraint_systems/manual_groth16.nim +++ b/constantine/proof_systems/constraint_systems/manual_groth16.nim @@ -91,7 +91,6 @@ proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): B_p = beta2.getJacobian + ctx.s * delta2 let Bs = ctx.zkey.B2 - doAssert Bs.len == wt.len # could compute via MSM for i in 0 ..< Bs.len: @@ -108,7 +107,6 @@ proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): # Get the B1 data let Bs = ctx.zkey.B1 - doAssert Bs.len == wt.len for i in 0 ..< Bs.len: result += wt[i] * Bs[i] @@ -216,14 +214,12 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW cw += priv[i] * Cs[i] let Hs = ctx.zkey.H - doAssert Hs.len == jabc.len var resH: EC_ShortW_Jac[Fp[Name], G1] for i in 0 ..< Hs.len: resH += jabc[i] * Hs[i] let delta1 = g16h.delta1 - # Declare `C_p` for the result var C_p: EC_ShortW_Jac[Fp[Name], G1] C_p = ctx.s * A_p + ctx.r * B1_p - (ctx.r * ctx.s) * delta1 + cw + resH From 37eb08ed1fc2041c4cfbfed062b817425bd0a0e4 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 17:30:18 +0200 Subject: [PATCH 11/49] clean up FFT for finite fields --- constantine/math/polynomials/fft_fields.nim | 42 +++++++------------ .../polynomials/fft_finite_field_test.nim | 3 -- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/constantine/math/polynomials/fft_fields.nim b/constantine/math/polynomials/fft_fields.nim index 516a3b7d7..d998d35e4 100644 --- a/constantine/math/polynomials/fft_fields.nim +++ b/constantine/math/polynomials/fft_fields.nim @@ -32,7 +32,7 @@ type FFT_Descriptor*[F] = object # `F` is either `Fp[Name]` or `Fr[Name]` ## Metadata for FFT on Elliptic Curve order*: int - rouGen*: F #getBigInt(F) + rouGen*: F ## Roots of unity generator based on primitive root of `F`: `ω = g^( (p - 1) // n )` rootsOfUnity*: ptr UncheckedArray[getBigInt(F)] # `getBigInt` gives us the right type depending on Fr/Fp ## domain, starting and ending with 1, length is cardinality+1 ## This allows FFT and inverse FFT to use the same buffer for roots. @@ -43,12 +43,6 @@ func computeRootsOfUnity[F](ctx: var FFT_Descriptor[F], generatorRootOfUnity: au ctx.rootsOfUnity[0].setOne() - debugecho "Generator ROU: ", generatorRootOfUnity.toHex() - var res = generatorRootOfUnity - let p = getBigInt(F).fromDecimal($ctx.order) - pow(res, p) - debugecho "To pow ? ", res.toHex() - var cur = generatorRootOfUnity for i in 1 .. ctx.order: ctx.rootsOfUnity[i].fromField(cur) @@ -64,10 +58,11 @@ func init*[Name: static Algebra](T: type FFT_Descriptor, order: int, generatorRo result.computeRootsOfUnity(generatorRootOfUnity) - for i in 0 ..< result.order: - debugecho "ω^", i, " = ", result.rootsOfUnity[i].toHex() - proc rootOfUnityGenerator*[F](_: typedesc[F], order: int): F = + ## Computes a root of unity generator for the order `n`, using + ## `ω = g^( (p - 1) // n )` where `g` is the primitive root + ## of the field `F`. + ## ## `p` = prime of the field (or order of subgroup) ## `n` = FFT order ## Highlighted the part we compute in each comment. @@ -79,36 +74,30 @@ proc rootOfUnityGenerator*[F](_: typedesc[F], order: int): F = # ω = g^( (p - 1) // `n` ) var n = F.fromInt(order.uint64) - #echo "n = ", n.toHex() # ω = g^( (p - 1) `// n` ) n.inv() - #echo "Inverted? ", n.toHex() # ω = g^( `(p - 1) // n` ) exponent *= n - #echo "Exp? ", exponent.toHex() var g: F = F.fromUint(primitiveRoot(F.Name).uint64) - # ω = `g^( (p - 1) // n` ) + # ω = `g^( (p - 1) // n )` g.pow_vartime(toBig(exponent)) - #echo "g ? ", g.toHex() result = g proc init*(T: typedesc[FFT_Descriptor], order: int): T = - ## For example for GF(13) and n == 4: - ## In backticks the part we currently compute + ## Initialize an `FFT_Descriptor` for the given `order`. The root of unity generator is + ## computed automatically. However, a primitive root is required for the field over which + ## the FFT is to be done. See `fft_lut.nim` for definitions and more information. let g = rootOfUnityGenerator(T.F, order) - #let g2 = scaleToRootOfUnity(T.F.Name) # [28 - order] - #for i, el in g2: - # debugecho i, " = ", el.toHex() result = T.init(order, g) func delete*(ctx: FFT_Descriptor) = ctx.rootsOfUnity.freeHeapAligned() -proc toFr[S: static int, Name: static Algebra](x: BigInt[S], isMont = true): Fr[Name] = +proc toFr[S: static int, Name: static Algebra](x: BigInt[S]): Fr[Name] = result.fromBig(x) -proc toFp[S: static int, Name: static Algebra](x: BigInt[S], isMont = true): Fp[Name] = +proc toFp[S: static int, Name: static Algebra](x: BigInt[S]): Fp[Name] = result.fromBig(x) proc toF[F; S: static int](T: typedesc[F], x: BigInt[S]): auto = @@ -128,10 +117,7 @@ func simpleFT[F; bits: static int]( var last {.noInit.}, v {.noInit.}: F var v0w0 {.noinit}: F - var v0w0In {.noInit.} = vals[0] - static: echo "TYE ??? ", F.Name, " is fp ? ", F is Fp[Fake13] - ## XXX: THER IS NO `prod` WITH ONLY 1 EXTRA ARG - v0w0.prod(v0w0In, F.toF(rootsOfUnity[0])) + v0w0.prod(vals[0], F.toF(rootsOfUnity[0])) for i in 0 ..< L: last = v0w0 @@ -223,7 +209,7 @@ func fft_vartime*[F](vals: openarray[F]): seq[F] = result = newSeq[F](order) let status = fftDesc.fft_vartime(result, vals) - doAssert (status == FFTS_Success).bool, "FFT failed." + doAssert (status == FFTS_Success).bool, "FFT failed with " & $status proc ifft_vartime*[F](vals: openarray[F]): seq[F] = ## Performs an inverse FFT on the given values and returns a seq of the result. @@ -236,4 +222,4 @@ proc ifft_vartime*[F](vals: openarray[F]): seq[F] = result = newSeq[F](order) let status = fftDesc.ifft_vartime(result, vals) - doAssert (status == FFTS_Success).bool, "FFT failed." + doAssert (status == FFTS_Success).bool, "FFT failed with " & $status diff --git a/constantine/math/polynomials/fft_finite_field_test.nim b/constantine/math/polynomials/fft_finite_field_test.nim index 8a4f7be94..5526823d5 100644 --- a/constantine/math/polynomials/fft_finite_field_test.nim +++ b/constantine/math/polynomials/fft_finite_field_test.nim @@ -17,9 +17,6 @@ import type EC = EC_ShortW_Jac[Fp[BLS6_6], G1] - -#echo getBigInt(F.Name, kBaseField) - suite "FFT Tests": test "FFT over Finite Field Fp[Fake13]": From 460378396e95d57a4c2aa64e24f7d18ec655c787 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 17:30:56 +0200 Subject: [PATCH 12/49] remove old FFT finite field test file --- .../polynomials/fft_finite_field_test.nim | 159 ------------------ 1 file changed, 159 deletions(-) delete mode 100644 constantine/math/polynomials/fft_finite_field_test.nim diff --git a/constantine/math/polynomials/fft_finite_field_test.nim b/constantine/math/polynomials/fft_finite_field_test.nim deleted file mode 100644 index 5526823d5..000000000 --- a/constantine/math/polynomials/fft_finite_field_test.nim +++ /dev/null @@ -1,159 +0,0 @@ -import - unittest, - constantine/math/arithmetic, - constantine/math/io/io_fields, - #constantine/math/elliptic/ec_shortweierstrass, - #constantine/named/zoo_generators, - constantine/named/[algebras, properties_fields, properties_curves], - constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], - #constantine/math/constants/zoo_fields, - #constantine/math/constants/zoo_subgroups, - #constantine/protocols/bls_signatures, - # Assume the FFT implementations are in these modules: - ./fft_fields, - ./fft - #fft_elliptic_curve - -type - EC = EC_ShortW_Jac[Fp[BLS6_6], G1] - -suite "FFT Tests": - test "FFT over Finite Field Fp[Fake13]": - - type F = Fp[Fake13] - - # Fr[Fake13] is GF(13) - let order = 4 # We'll use a small order for the test - # `5` is a generator for the order `4`, because `5^4 mod 13 = 1`: - # `5^0 mod 13 = 1 ` - # `5^1 mod 13 = 5 ` - # `5^2 mod 13 = 12` - # `5^3 mod 13 = 8 ` - # `5^4 mod 13 = 1 ` - let Gen = F.fromUInt(5'u64) - var fftDesc = FFTDescriptor[Fp[Fake13]].init(order, Gen) - defer: fftDesc.delete() - - # Input values - # Describes Polynomial: - # P(x) = a₀ + a₁·x + a₂·x² + a₃·x³ - # P(x) = 1 + 2·x + 3·x² + 4·x³ - var input = @[F.fromInt(1), F.fromInt(2), F.fromInt(3), F.fromInt(4)] - var output = newSeq[F](order) - - # Perform forward FFT - # The forward FFT returns the evaluation of polynomial P(x) at the - # roots of unity based on `Gen` - let status = fftDesc.fft_vartime(output, input) - check (status == FFTS_Success).bool - - # Expected output (calculated manually or with a known-good implementation) - let expected = @[ - # let `g(i) = 5^i mod 13` # where `5` is the generator, i.e. `5^4 mod 13 = 1` - F.fromUInt(10'u64), # P(g(0)) = (1 + 2·1 + 3·1² + 4·1³ ) mod 13 = 10 - F.fromUInt(1'u64), # P(g(1)) = (1 + 2·5 + 3·5² + 4·5³ ) mod 13 = 1 - F.fromUInt(11'u64), # P(g(2)) = (1 + 2·12 + 3·12² + 4·12³) mod 13 = 11 - F.fromUInt(8'u64) # P(g(3)) = (1 + 2·8 + 3·8² + 4·8³ ) mod 13 = 8 - ] - - for i in 0 ..< output.len: - echo "Output: ", output[i].toHex() - echo "Expect: ", expected[i].toHex() - check (output[i] == expected[i]).bool - - # Perform inverse FFT - var inverse_output = newSeq[F](order) - let inverse_status = fftDesc.ifft_vartime(inverse_output, output) - check (inverse_status == FFTS_Success).bool - - # Check if we get back the original input - for i in 0 ..< order: - echo "Inverse results in: ", inverse_output[i].toDecimal() - check (inverse_output[i] == input[i]).bool - - test "FFT over finite field Fr[BN254_Snarks]": - type F = Fr[BN254_Snarks] - - const order = 4 - var fftDesc = FFTDescriptor[Fr[BN254_Snarks]].init(order) - defer: fftDesc.delete() - - - echo fftDesc.rouGen.toHex() - - # Input values - # Describes Polynomial: - # P(x) = a₀ + a₁·x + a₂·x² + a₃·x³ - # P(x) = 1 + 2·x + 3·x² + 4·x³ - var input = @[F.fromInt(1), F.fromInt(2), F.fromInt(3), F.fromInt(4)] - var output = newSeq[F](order) - - # Perform forward FFT - # The forward FFT returns the evaluation of polynomial P(x) at the - # roots of unity based on `Gen` - let status = fftDesc.fft_vartime(output, input) - check (status == FFTS_Success).bool - - for i in 0 ..< output.len: - echo "Output: ", output[i].toHex() - #echo "Expect: ", expected[i].toHex() - #check (output[i] == expected[i]).bool - - # Perform inverse FFT - var inverse_output = newSeq[F](order) - let inverse_status = fftDesc.ifft_vartime(inverse_output, output) - check (inverse_status == FFTS_Success).bool - - # Check if we get back the original input - for i in 0 ..< order: - echo "Inverse results in: ", inverse_output[i].toDecimal() - check (inverse_output[i] == input[i]).bool - - -## XXX: Also broken! Same as `fft.nim` main part! -# test "FFT over Elliptic Curve EC[Fp[BLS6_6], G1]": -# proc getROU(): Fr[BLS6_6] = -# result = Fr[BLS6_6].fromUint(3'u32) -# -# proc getGenG1(): EC = -# let fx = Fp[BLS6_6].fromUint(13'u32) -# let fy = Fp[BLS6_6].fromUint(15'u32) -# var gen {.noinit.}: EC_ShortW_Aff[Fp[BLS6_6], G1] -# gen.x = fx -# gen.y = fy -# result = gen.getJacobian() -# -# -# let order = 4 # We'll use a small order for the test -# var fftDesc = ECFFTDescriptor[EC].new(order, getROU()) -# defer: fftDesc.delete() -# -# # Input values (multiples of the generator point) -# var input = newSeq[EC](order) -# let generator = getGenG1() #BLS6_6.getGenerator("G1") -# for i in 0 ..< order: -# input[i].scalarMul(Fr[BLS6_6].fromInt((i + 1).uint), generator) -# -# var output = newSeq[EC](order) -# -# # Perform forward EC-FFT -# let status = fftDesc.fft_vartime(output, input) -# check(status == FFTS_Success) -# -# # Expected output (calculated manually or with a known-good implementation) -# # Note: This would depend on the specific curve parameters and roots of unity used -# # For this test, we'll just check some properties instead of exact values -# -# # Check that the output points are on the curve -# for point in output: -# check(point.isOnCurve()) -# -# # Perform inverse EC-FFT -# var inverse_output = newSeq[EC](order) -# let inverse_status = fftDesc.ifft_vartime(inverse_output, output) -# check(inverse_status == FFTS_Success) -# -# # Check if we get back the original input -# for i in 0 ..< order: -# check(inverse_output[i] == input[i]) -# From d8359454cb29862924ace822f394e95b963f9bbf Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 17:54:35 +0200 Subject: [PATCH 13/49] remove old TODO from FFT --- constantine/math/polynomials/fft.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/constantine/math/polynomials/fft.nim b/constantine/math/polynomials/fft.nim index 54a0c04e0..9ef64faaf 100644 --- a/constantine/math/polynomials/fft.nim +++ b/constantine/math/polynomials/fft.nim @@ -99,7 +99,6 @@ func fft_internal[EC; bits: static int]( for i in 0 ..< half: # FFT Butterfly y_times_root .scalarMul_vartime(rootsOfUnity[i], output[i+half]) - ## XXX: is this correct or need to be args exchanged? output[i+half] .diff_vartime(output[i], y_times_root) output[i] .sum_vartime(output[i], y_times_root) From 80344bdba9ab63ef9634fa1428bb7490e09c59b3 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 18:08:31 +0200 Subject: [PATCH 14/49] move groth16 files one dir up (proof_systems) --- .../{constraint_systems => }/groth16_utils.nim | 8 +------- .../{constraint_systems => }/manual_groth16.nim | 0 2 files changed, 1 insertion(+), 7 deletions(-) rename constantine/proof_systems/{constraint_systems => }/groth16_utils.nim (87%) rename constantine/proof_systems/{constraint_systems => }/manual_groth16.nim (100%) diff --git a/constantine/proof_systems/constraint_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim similarity index 87% rename from constantine/proof_systems/constraint_systems/groth16_utils.nim rename to constantine/proof_systems/groth16_utils.nim index f6db40161..b17f1e328 100644 --- a/constantine/proof_systems/constraint_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -14,18 +14,12 @@ proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = else: result.fromBig(b) -proc toFr*[Name: static Algebra](x: seq[byte], isMont = true, isDoubleMont = false): Fr[Name] = +proc toFr*[Name: static Algebra](x: seq[byte], isMont = true): Fr[Name] = let b = matchingOrderBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian) if isMont: var bN: typeof(b) bN.fromMont(b, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) result.fromBig(bN) - elif isDoubleMont: - var bN: typeof(b) - bN.fromMont(b, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) - var bNN: typeof(b) - bNN.fromMont(bN, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) - result.fromBig(bNN) else: result.fromBig(b) diff --git a/constantine/proof_systems/constraint_systems/manual_groth16.nim b/constantine/proof_systems/manual_groth16.nim similarity index 100% rename from constantine/proof_systems/constraint_systems/manual_groth16.nim rename to constantine/proof_systems/manual_groth16.nim From 0c41a2e30cbd7a7264c4deecbf39438adb31bc2f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 18:08:47 +0200 Subject: [PATCH 15/49] add notes about binary zkey, wtns file format / parser --- .../constraint_systems/wtns_binary_parser.nim | 17 ++++++----- .../constraint_systems/zkey_binary_parser.nim | 30 +++++++++++++------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index 734909336..f08aa91e3 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -12,9 +12,16 @@ import ../../serialization/[io_limbs, parsing], constantine/platforms/[fileio, abstractions], ../../named/algebras, # Fr - ./groth16_utils + ../groth16_utils #[ +The following is a rough spec of the witness files. Details may vary for different +curves (e.g. field witnesse elements may have different sizes). + +Given that there is no specification for the `.wtns` file format, we assume it +is generally treated like the R1CS binary files. See the note at the top of +`zkey_binary_parser.nim` for more notes. + 1. File Header: - Magic String: - Offset: 0 bytes @@ -86,9 +93,6 @@ type header*: WitnessHeader witnesses*: seq[Fr[Name]] -## XXX: Add `Wtns[T]` type, which takes care of converting field elements and -## does not contain `seq[Section]` anymore - func header*(wtns: WtnsBin): WitnessHeader = result = wtns.sections.filterIt(it.sectionType == kHeader)[0].header @@ -98,7 +102,7 @@ func witnesses*(wtns: WtnsBin): seq[Witness] = proc getWitnesses[Name: static Algebra](witnesses: seq[Witness]): seq[Fr[Name]] = result = newSeq[Fr[Name]](witnesses.len) for i, w in witnesses: - result[i] = toFr[Name](w.data, isMont = false) ## Improtant: Witness does *not* store numbers in Montgomery rep + result[i] = toFr[Name](w.data, isMont = false) ## Important: Witness does *not* store numbers in Montgomery rep proc toWtns*[Name: static Algebra](wtns: WtnsBin): Wtns[Name] = result = Wtns[Name]( @@ -181,6 +185,3 @@ proc parseWtnsFile*(path: string): WtnsBin = result.sections.add s fileio.close(f) - -when isMainModule: - echo parseWtnsFile("/home/basti/org/constantine/moonmath/circom/three_fac_js/witness.wtns") diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index 4894c5c91..b46e8bbff 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -14,11 +14,29 @@ import ../../named/algebras, # Fr, Fp ../../math/extension_fields, # Fp2 ../../math/elliptic/[ec_shortweierstrass_affine], # EC types - ./groth16_utils # to unmarshal data + ../groth16_utils # to unmarshal data from std / sequtils import filterIt from std / strutils import endsWith +## Note on the parsing logic: +## +## A Zkey file is first parsed into the `ZkeyBin` type. This data type +## tries to mostly match the binary format of the `.zkey` files. +## *HOWEVER*, there is no specification for `.zkey` files. *BUT*, if we +## assume the same general specification holds as for the R1CS binary +## files (for which +## https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md +## exists), we must assume that each section can appear in arbitrary +## order in the binary file. Thus, we use an approach with variant objects +## and simply a `seq[Section]` for the `ZkeyBin` type. We could either +## disregard "spec compliance" and just assume the section order is +## based on the section numbering (i.e. like the `ZkeySectionKind` enum +## values) or handle different orders during parsing. +## +## In the end, we have a curve specific "typed" `Zkey[T]` type, which +## does away with this approach anyhow. + type ZkeySectionKind* = enum # `kInvalid` used to indicate unset & to make the enum work as field discriminator kInvalid = 0 @@ -157,6 +175,8 @@ type H*: H[Name] contr*: Contributions +## NOTE: These are rather ugly, but a result of the parsing approach we use. +## See note at the top of the file. func header*(zkey: ZkeyBin): Header = result = zkey.sections.filterIt(it.sectionType == kHeader)[0].header @@ -373,11 +393,3 @@ proc parseZkeyFile*(path: string): ZkeyBin = result.sections.add s fileio.close(f) - -when isMainModule: - #echo parseZkeyFile("/tmp/test.zkey") - #echo parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac0000.zkey") - let zkey = parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") - echo zkey - - echo zkey.toZkey[:BN254_Snarks]() From 47b20203faf3be770f3a5c6441dd36852f0db81f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 14 Aug 2024 18:19:12 +0200 Subject: [PATCH 16/49] fixup code for changed path, minor cleanup --- constantine/proof_systems/groth16_utils.nim | 31 ++++++++++---------- constantine/proof_systems/manual_groth16.nim | 31 +++++++------------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index b17f1e328..3f87c64c7 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -1,10 +1,10 @@ -import ../../math/[arithmetic, extension_fields] -import ../../math/io/[io_bigints, io_fields, io_ec, io_extfields] -import ../../platforms/abstractions -import ../../named/[algebras, properties_fields, properties_curves] -import ../../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime] +import ../math/[arithmetic, extension_fields], + ../math/io/[io_bigints, io_fields, io_ec, io_extfields], + ../platforms/abstractions, + ../named/[algebras, properties_fields, properties_curves], + ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime] -## NOTE: These constructors for ... +## Helper constructors for Fp / Fr elements used in Groth16 binary file parsers. proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = let b = matchingBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian) if isMont: @@ -46,16 +46,6 @@ proc toEcG2*[Name: static Algebra](s: seq[byte]): EC_ShortW_Aff[Fp2[Name], G2] = if not bool(result.isNeutral()): doAssert isOnCurve(result.x, result.y, G2).bool, "Input point is not on curve!" -## Currently not used -proc randomFieldElement*[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] = - ## random element in ~Fr[Name]~ - let m = Fr[Name].getModulus() - var b: matchingOrderBigInt(Name) - - while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? - assert b.limbs.sysrand() - result.fromBig(b) - proc asEC*[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp[Name]]): seq[EC_ShortW_Aff[Fp[Name], G1]] = result = newSeq[EC_ShortW_Aff[Fp[Name], G1]](pts.len) for i, el in pts: @@ -65,3 +55,12 @@ proc asEC2*[Name: static Algebra](pts: seq[seq[byte]], _: typedesc[Fp2[Name]]): result = newSeq[EC_ShortW_Aff[Fp2[Name], G2]](pts.len) for i, el in pts: result[i] = toEcG2[Name](el) + +proc randomFieldElement*[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] = + ## random element in ~Fr[Name]~ + let m = Fr[Name].getModulus() + var b: matchingOrderBigInt(Name) + + while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? + assert b.limbs.sysrand() + result.fromBig(b) diff --git a/constantine/proof_systems/manual_groth16.nim b/constantine/proof_systems/manual_groth16.nim index c59004bb4..abf126580 100644 --- a/constantine/proof_systems/manual_groth16.nim +++ b/constantine/proof_systems/manual_groth16.nim @@ -1,16 +1,16 @@ -import ./r1cs_circom_parser, - ./zkey_binary_parser, - ./wtns_binary_parser +import ./constraint_systems/r1cs_circom_parser, + ./constraint_systems/zkey_binary_parser, + ./constraint_systems/wtns_binary_parser -import ../../math/[arithmetic, extension_fields] -import ../../math/io/[io_bigints, io_fields, io_ec, io_extfields] -import ../../platforms/abstractions -import ../../named/[algebras, properties_fields, properties_curves] -import ../../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime] -import ../../named/zoo_generators -import ../../csprngs/sysrand +import ../math/[arithmetic, extension_fields], + ../math/io/[io_bigints, io_fields, io_ec, io_extfields], + ../platforms/abstractions, + ../named/[algebras, properties_fields, properties_curves], + ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], + ../named/zoo_generators, + ../csprngs/sysrand -import ../../math/polynomials/[fft_fields, fft_lut] +import ../math/polynomials/[fft_fields, fft_lut] from std / math import log2 @@ -26,15 +26,6 @@ type r: Fr[Name] s: Fr[Name] -proc randomFieldElement[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] = - ## random element in ~Fp[Name]~ - let m = Fr[Name].getModulus() - var b: matchingOrderBigInt(Name) - - while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? - assert b.limbs.sysrand() - result.fromBig(b) - proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: Zkey[Name], wtns: Wtns[Name], r1cs: R1CS): Groth16Prover[Name] = result = Groth16Prover[Name]( zkey: zkey, From ed80dafb22f9ca1611da8e42fc30669fe5143b72 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 12:39:10 +0200 Subject: [PATCH 17/49] bind sysrand in utils --- constantine/proof_systems/groth16_utils.nim | 5 ++++- constantine/proof_systems/manual_groth16.nim | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index 3f87c64c7..ff916a64f 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -2,7 +2,9 @@ import ../math/[arithmetic, extension_fields], ../math/io/[io_bigints, io_fields, io_ec, io_extfields], ../platforms/abstractions, ../named/[algebras, properties_fields, properties_curves], - ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime] + ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], + ../csprngs/sysrand + ## Helper constructors for Fp / Fr elements used in Groth16 binary file parsers. proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = @@ -61,6 +63,7 @@ proc randomFieldElement*[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] let m = Fr[Name].getModulus() var b: matchingOrderBigInt(Name) + bind sysrand while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? assert b.limbs.sysrand() result.fromBig(b) diff --git a/constantine/proof_systems/manual_groth16.nim b/constantine/proof_systems/manual_groth16.nim index abf126580..d5c43bbc0 100644 --- a/constantine/proof_systems/manual_groth16.nim +++ b/constantine/proof_systems/manual_groth16.nim @@ -7,8 +7,7 @@ import ../math/[arithmetic, extension_fields], ../platforms/abstractions, ../named/[algebras, properties_fields, properties_curves], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], - ../named/zoo_generators, - ../csprngs/sysrand + ../named/zoo_generators import ../math/polynomials/[fft_fields, fft_lut] From b48f02f781b2d8930cdcc01406218d0f4d56872c Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 12:39:29 +0200 Subject: [PATCH 18/49] export some modules, export prover & prove --- constantine/proof_systems/manual_groth16.nim | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/constantine/proof_systems/manual_groth16.nim b/constantine/proof_systems/manual_groth16.nim index d5c43bbc0..7c4de9333 100644 --- a/constantine/proof_systems/manual_groth16.nim +++ b/constantine/proof_systems/manual_groth16.nim @@ -15,15 +15,21 @@ from std / math import log2 import ./groth16_utils +# Export so users can parse files +export r1cs_circom_parser, zkey_binary_parser, wtns_binary_parser +export groth16_utils +export arithmetic, extension_fields, abstractions, io_bigints, io_fields, io_ec, io_extfields + type - Groth16Prover[Name: static Algebra] = object + Groth16Prover*[Name: static Algebra] = object ## XXX: In the future the below should be typed objects that are already unmarshalled! - zkey: Zkey[Name] - wtns: Wtns[Name] - r1cs: R1CS + zkey*: Zkey[Name] + wtns*: Wtns[Name] + r1cs*: R1CS # secret random values `r`, `s` for the proof - r: Fr[Name] - s: Fr[Name] + ## XXX: These won't remain public of course + r*: Fr[Name] + s*: Fr[Name] proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: Zkey[Name], wtns: Wtns[Name], r1cs: R1CS): Groth16Prover[Name] = result = Groth16Prover[Name]( @@ -215,7 +221,7 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW C_p = ctx.s * A_p + ctx.r * B1_p - (ctx.r * ctx.s) * delta1 + cw + resH result = C_p -proc prove[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Jac[Fp[Name], G1], +proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Jac[Fp[Name], G1], B: EC_ShortW_Jac[Fp2[Name], G2], C: EC_ShortW_Jac[Fp[Name], G1]] = #[ From 38658f5cda4d393039f36a661d579542ffa2f50b Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 12:39:50 +0200 Subject: [PATCH 19/49] bind two identifiers in `sumImpl` Otherwise when performing operations on EC points in Jacobian, depending on import these can be missing. --- constantine/math/elliptic/ec_shortweierstrass_jacobian.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim b/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim index 05805cee7..80471b52d 100644 --- a/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim +++ b/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim @@ -337,6 +337,8 @@ template sumImpl[F; G: static Subgroup]( # if P or R were infinity points they would have spread 0 with Z₁Z₂ block: # Infinity points + bind isNeutral + bind ccopy o.ccopy(Q, P.isNeutral()) o.ccopy(P, Q.isNeutral()) From b174b1446eba333b8fdb83a152f3ca812382d78a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 12:40:40 +0200 Subject: [PATCH 20/49] [example] add Groth16 prover example --- examples/groth16_proof_example.nim | 51 ++++ examples/groth16_prover.org | 395 +++++++++++++++++++++++++++++ 2 files changed, 446 insertions(+) create mode 100644 examples/groth16_proof_example.nim create mode 100644 examples/groth16_prover.org diff --git a/examples/groth16_proof_example.nim b/examples/groth16_proof_example.nim new file mode 100644 index 000000000..630cc6a3c --- /dev/null +++ b/examples/groth16_proof_example.nim @@ -0,0 +1,51 @@ +import constantine/proof_systems/manual_groth16, + constantine/named/algebras +#import ../math/[arithmetic, extension_fields], +# ../math/io/[io_bigints, io_fields, io_ec, io_extfields], +# ../platforms/abstractions, +# ../named/[algebras, properties_fields, properties_curves], +# ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], +# ../named/zoo_generators, +# ../csprngs/sysrand +#import ./groth16_utils + +const T = BN254_Snarks + +let wtns = parseWtnsFile("./groth16_files/three_fac_js/witness.wtns").toWtns[:T]() +let zkey = parseZkeyFile("./groth16_files/three_fac_final.zkey").toZkey[:T]() +let r1cs = parseR1csFile("./groth16_files/three_fac.r1cs").toR1CS() + +var ctx = Groth16Prover[T].init(zkey, wtns, r1cs) + +const rSJ = @[ + byte 143, 55, 118, 73, 42, 115, 60, 77, + 95, 209, 41, 144, 250, 137, 138, 71, + 176, 242, 186, 232, 179, 30, 88, 255, + 198, 161, 182, 150, 220, 149, 33, 19 +] +const sSJ = @[ + byte 213, 105, 105, 27, 129, 249, 139, 158, + 221, 68, 37, 163, 59, 71, 19, 108, + 60, 153, 183, 156, 25, 148, 37, 9, + 85, 205, 250, 246, 132, 142, 244, 36 +] + +# construct the random element `r` from snarkjs "secret" r +let r = toFr[BN254_Snarks](rSJ) +# and `s` +let s = toFr[BN254_Snarks](sSJ) + +ctx.r = r +ctx.s = s + +let (A_p, B2_p, C_p) = ctx.prove() + +echo "\n==============================\n" +echo "A_p#16 = ", A_p.toHex() +echo "A_p#10 = ", A_p.toDecimal() +echo "------------------------------" +echo "B_p#16 = ", B2_p.toHex() +echo "B_p#10 = ", B2_p.toDecimal() +echo "------------------------------" +echo "C_p#16 = ", C_p.toHex() +echo "C_p#10 = ", C_p.toDecimal() diff --git a/examples/groth16_prover.org b/examples/groth16_prover.org new file mode 100644 index 000000000..81de36cd9 --- /dev/null +++ b/examples/groth16_prover.org @@ -0,0 +1,395 @@ +* Groth16 prover / verifier example + +In this example we will go through the full construction of a Groth16 +prove and verification. The current Constantine Groth16 prover logic +depends on SnarkJS / Circom in order to produce an arithmetic circuit +of the statement to be proven, turn it into an R1CS, compute its +witnesses and set up the common reference string. Our logic uses the +SnarkJS produced ~.r1cs~, ~.zkey~ and ~.wtns~ files as inputs. + +** 3-factorization problem description + +We will use an example from the Moonmath manual, +https://github.com/LeastAuthority/moonmath-manual +namely the "3-factorization problem", starting from example 115 and +used in further examples building from it throughout the book. The +idea of the example is simply to build a SNARK, which proves the +knowledge of three factors of an element from $\mathcal{F}_{13}$, i.e. + +\[ +x_1·x_2·x_3 = x_4. +\] + +where $x_1, x_2, x_3$ are hidden witnesses and $x_4$ acts as a public +instance. In our explanation here we will skip over any manual +derivations of a possible R1CS, calculation of the QAP (Quadratic +Arithmetic Program) or algebraic circuits. Instead, we will define the +problem in Circom and have it generate the R1CS for us. + +Note that we will be light on details in some parts, those can be read +up on in the Moonmath manual. See the list of all related examples in +Moonmath in sec. [[#sec:3fac_examples]] if you wish to learn more about +different parts. + +** Circom definition + +The Circom code to express the problem can be written as follows +(example 137): +#+begin_src circom +template Multiplier() { + signal input a ; + signal input b ; + signal output c ; + c <== a*b ; +} +template three_fac () { + signal input x1 ; + signal input x2 ; + signal input x3 ; + signal output x4 ; + component mult1 = Multiplier() ; + component mult2 = Multiplier() ; + mult1.a <== x1 ; + mult1.b <== x2 ; + mult2.a <== mult1.c ; + mult2.b <== x3 ; + x4 <== mult2.c ; +} +component main = three_fac() ; +#+end_src + +If we save this as ~three_fac.circom~ we can use Circom to generate +the corresponding R1CS by: +#+begin_src sh :exports both +circom three_fac.circom --r1cs --wasm --sym +#+end_src + +#+RESULTS: +| template | instances: | 2 | +| non-linear | constraints: | 2 | +| linear | constraints: | 0 | +| public | inputs: | 0 | +| private | inputs: | 3 | +| public | outputs: | 1 | +| wires: | 6 | | +| labels: | 11 | | +| Written | successfully: | ./three_fac.r1cs | +| Written | successfully: | ./three_fac.sym | +| Written | successfully: | ./three_fac_js/three_fac.wasm | +| Everything | went | okay | + +This produces 3 output files, a ~.r1cs~ file and a ~.sym~ +file. Importantly, it also generates a new directory (in our case +called ~three_fac_js~, which contains a ~.wasm~ WebAssembly binary, as +well as 2 JavaScript source files. ~generate_witness.js~ will later be +used to generate a ~.wtns~ file from some inputs used in the proof and +~witness_calculator.js~ containing most of the actual logic. + +Using SnarkJS we can print information about the R1CS file: +#+begin_src sh :results drawer :exports both +snarkjs r1cs info groth16_files/three_fac.r1cs +#+end_src + +#+RESULTS: +:results: +[INFO] snarkJS: Curve: bn-128 +[INFO] snarkJS: # of Wires: 6 +[INFO] snarkJS: # of Constraints: 2 +[INFO] snarkJS: # of Private Inputs: 3 +[INFO] snarkJS: # of Public Inputs: 0 +[INFO] snarkJS: # of Labels: 11 +[INFO] snarkJS: # of Outputs: 1 +:end: + +where we see the number of produced wires the corresponding algebraic +circuit has, the number of constraints in the R1CS as well as the +private and public inputs. + +** Common reference string / ~.zkey~ file construction + +From here we can use SnarkJS to (please read example 150 for more +details on these commands): +1. First perform a "powers of tau" ceremony, where optionally multiple + parties can contribute randomness to the common reference string of + the τ parameter. +2. Using the powers of tau, set up a Groth16 prove using our R1CS. +3. Again, multiple people can contribute randomness to the parameters + α, β, γ, δ that are part of the Groth16 common reference string. + +For part 1, we perform steps like the following: +#+begin_src sh +snarkjs powersoftau new bn128 4 pot4_0000.ptau -v +snarkjs powersoftau contribute pot4_0000.ptau pot4_0001.ptau -name="1st_cont" -v +snarkjs powersoftau verify pot4_0001.ptau +snarkjs powersoftau beacon pot4_0001.ptau pot4_beacon.ptau +snarkjs powersoftau prepare phase2 pot4_beacon.ptau pot4_final.ptau -v +snarkjs powersoftau verify pot4_final.ptau +#+end_src + +To set up the Groth16 proof, we run: +#+begin_src sh +snarkjs groth16 setup three_fac.r1cs pot4_final.ptau three_fac0000.zkey +#+end_src + +And finally we add randomness to the ~.zkey~ files, finalize, +verify it and export the verification key as JSON: +#+begin_src sh +snarkjs zkey contribute three_fac0000.zkey three_fac0001.zkey -name="1st Contributor Name" -v +snarkjs zkey verify three_fac.r1cs pot4_final.ptau three_fac0001.zkey +snarkjs zkey beacon three_fac0001.zkey three_fac_final.zkey 010203040506070809 10 -n="Final Beacon phase2" +snarkjs zkey verify three_fac.r1cs pot4_final.ptau three_fac_final.zkey +snarkjs zkey export verificationkey three_fac_final.zkey verification_key.json +#+end_src + +** Witness ~.wtns~ file construction + +With this we have 2 of the 3 required inputs to compute a Groth16 +proof in Constantine, the ~.r1cs~ file and the ~.zkey~ file. Now we +still need a Witness ~.wtns~ file. As alluded to earlier, +~generate_witness.js~ is used to generate that file. However, to +generate the file, we first and foremost need to define the private +witnesses that should be contained in it. In a normal context this +would be the values the prover intends to proof it as knowledge of. In +our case here, we will simply use 3 'randomly' chosen integers of the +field BN254 corresponding to the variables $x_1, x_2, x_3$ and store them in a JSON file: + +#+begin_src json +{ +"x1": 266454826700390499788624045644422204835838308568801104096964341478260924069, +"x2": 17022543691211744762566166588937408281011290768059146405469762658080007243141, +"x3": 2169708499392809782734482748125393322939898426476751716891099115492318742078 +} +#+end_src + +Stored as ~input.json~, we then run the JS code using ~node~: +#+begin_src sh +cd groth16_files/three_fac_js +node generate_witness.js three_fac.wasm input.json witness.wtns +#+end_src + +#+RESULTS: + +** SnarkJS as a Groth16 prover + +Now we can first use SnarkJS to run the Groth16 prover on our files: + +#+begin_src sh +cd groth16_files +snarkjs groth16 prove three_fac_final.zkey three_fac_js/witness.wtns proof.json public.json +#+end_src +which produces ~proof.json~: +#+begin_src json +{ + "pi_a": [ + "5525629793372463776337933283524928112323589665400780041477380790923758613749", + "21229177076048503863699135039723099340209138028149442778064006577287317302601", + "1" + ], + "pi_b": [ + [ + "10113559933709853115219982658131344715329670532374721861173670433756614595086", + "748111067660143353202076805159132563350177510079329482395824347599610874338" + ], + [ + "14193926223452546125681093394065339196897041249946578591171606543100010486627", + "871256420758854731396810855688710623510558493821614150596755347032202324148" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "18517653609733492682442099361591955563405567929398531111532682405176646276349", + "17315036348446251361273519572420522936369550153340386126725970444173389652255", + "1" + ], + "protocol": "groth16", + "curve": "bn128" +} +#+end_src +and ~public.json~: +#+begin_src json +[ + "9539182767316925183286892436718181010853851464478187124330950611358943415507" +] +#+end_src + +** Constantine as a Groth16 prover + +The Groth16 prover of Constantine lives in +[[file:../constantine/proof_systems/manual_groth16.nim]]. If we use the +previously produced ~.r1cs~, ~.zkey~ and ~.wtns~ files to produce a +Groth16 proof with it, the output numbers will differ from the SnarkJS +results necessarily. This is because as part of the proving phase, the +prover chooses two additional random field elements ~r~, ~s~ (or +sometimes called ~r~ and ~t~). However, in order to make it +deterministic, we have extracted the random elements from SnarkJS and +will reuse them in Constantine now. + +Let's start by preparing all of our imports: +#+begin_src nim :tangle groth16_proof_example.nim +import constantine/proof_systems/manual_groth16, + constantine/named/algebras +#+end_src + +The name of the ~alt_bn128~ / ~BN254~ curve in Constantine is +~BN254_Snarks~. For convenience we will assign it to a shorter constant: + +#+begin_src nim :tangle groth16_proof_example.nim +const T = BN254_Snarks +#+end_src + +Next, we parse the three binary files. The first ~parseFooFile~ +command returns a "raw" binary data file, which contains all field +elements still as ~seq[byte]~ data. We can convert it to a more +"typed" expression using ~toFoo~ and handing the target curve: + +#+begin_src nim :tangle groth16_proof_example.nim +let wtns = parseWtnsFile("./groth16_files/three_fac_js/witness.wtns").toWtns[:T]() +let zkey = parseZkeyFile("./groth16_files/three_fac_final.zkey").toZkey[:T]() +let r1cs = parseR1csFile("./groth16_files/three_fac.r1cs").toR1CS() +#+end_src + +With these we can construct our Groth16 prover context object: + +#+begin_src nim :tangle groth16_proof_example.nim +var ctx = Groth16Prover[T].init(zkey, wtns, r1cs) +#+end_src + +Now we define the two constants ~r~ and ~s~ based on the bytes +extracted from SnarkJS and unmarshal them into field elements of the +G1 subgroup of BN254: +#+begin_src nim :tangle groth16_proof_example.nim +const rSJ = @[ + byte 143, 55, 118, 73, 42, 115, 60, 77, + 95, 209, 41, 144, 250, 137, 138, 71, + 176, 242, 186, 232, 179, 30, 88, 255, + 198, 161, 182, 150, 220, 149, 33, 19 +] +const sSJ = @[ + byte 213, 105, 105, 27, 129, 249, 139, 158, + 221, 68, 37, 163, 59, 71, 19, 108, + 60, 153, 183, 156, 25, 148, 37, 9, + 85, 205, 250, 246, 132, 142, 244, 36 +] + +# construct the random element `r` from snarkjs "secret" r +let r = toFr[BN254_Snarks](rSJ) +# and `s` +let s = toFr[BN254_Snarks](sSJ) +#+end_src + +Now all we need to do is overwrite the random field elements already +created. *IMPORTANT: IN A REAL USE CASE YOU WOULD NEVER DO THIS OF COURSE!* +#+begin_src nim :tangle groth16_proof_example.nim +ctx.r = r +ctx.s = s +#+end_src + +Let's run the proof and see if the output matches the SnarkJS output: +#+begin_src nim :tangle groth16_proof_example.nim :exports both +let (A_p, B2_p, C_p) = ctx.prove() + +echo "A_p#16 = ", A_p.toHex() +echo "A_p#10 = ", A_p.toDecimal() +echo "------------------------------" +echo "B_p#16 = ", B2_p.toHex() +echo "B_p#10 = ", B2_p.toDecimal() +echo "------------------------------" +echo "C_p#16 = ", C_p.toHex() +echo "C_p#10 = ", C_p.toDecimal() +#+end_src + +:RESULTS: +A_p#16 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( + x: 0x0c37654828f4fa92099b6e6bc3ef1e233688e29775ad84a587e3e0a6b94734f5, + y: 0x2eef49d5d85978033554eeadf6a3af464fd201d8c6687fdb51fc7f1077622d49 +) +A_p#10 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( + x: 05525629793372463776337933283524928112323589665400780041477380790923758613749, + y: 21229177076048503863699135039723099340209138028149442778064006577287317302601 +) +------------------------------ +B_p#16 = EC_ShortW_Jac[Fp2[BN254_Snarks], G2]( + x: Fp2( + c0: 0x165c12731d58092618243a9a78339604e2c99771f27afb7f16083a1f5425920e, + c1: 0x01a76a75bc51d03cbf331de519dbe6b8b0d5115f4e3cbcc2c0833faba7cc89e2), + y: Fp2( + c0: 0x1f617a40811c35355866ab3987b0c87e39f84b749fe79489a2a8c1a33642db63, + c1: 0x01ed1d18bf3e70d11a0ec6c87da9c8296736fefa40f876ea6ae5d0df4520a8b4) +) +B_p#10 = EC_ShortW_Jac[Fp2[BN254_Snarks], G2]( + x: Fp2( + c0: 10113559933709853115219982658131344715329670532374721861173670433756614595086, + c1: 00748111067660143353202076805159132563350177510079329482395824347599610874338), + y: Fp2( + c0: 14193926223452546125681093394065339196897041249946578591171606543100010486627, + c1: 00871256420758854731396810855688710623510558493821614150596755347032202324148) +) +------------------------------ +C_p#16 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( + x: 0x1bb0c6fadbe2fe02b17d3da128f9a0aef119640c09f1ec52e17431115ddcedad, + y: 0x07439985d180eaa34951b45d430e02e5ba1bcc4d2b29dffd90171f1aa88c6a1e +) +C_p#10 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( + x: 12524785304069290509448413767699932740828656355192528146073107099016111123885, + y: 03285628268350284309020370879913195676683658549907819489051412512121524349470 +) +:END: + +As we can see when comparing the decimal outputs, the numbers match. + +*XXX*: RIGHT NOW C DOES NOT MATCH! + +This concludes this very quick introduction on the requirements and +how to compute a Groth16 proof with Constantine. + +** List of all 3-factorization examples in Moonmath +:PROPERTIES: +:CUSTOM_ID: sec:3fac_examples +:END: + +Just for reference, the full list of examples about the +3-factorization problem are (as of Moonmath of +<2024-08-14 Wed 18:44>) listed below. It is a useful overview to +understand the relevant steps. + +Examples: +- 115 (Language, L_3,fac) +- 118 (Decision function, R_3) + I_1 = W_1 · W_2 · W_3 +- 120 (R1CS), flatten equation: + W_1 · W_2 = W_4 + W_4 · W_3 = I_1 +- 122 (Explicit R1CS satisfying example) +- 124 (Algebraic circuit construction) + f_3,fac(x_1,x_2,x_3) = MUL(MUL(x_1, x_2), x_3) +- 126 (Example assignment to algebraic circuit) +- 128 (Example / note on circuit execution to find input witnesses & + compute W_4 by execution) +- 129 (Transform algebraic circuit back into R1CS following rules) +- 131 (QAP from R1CS): + QAP(R_3.fac_zk ) = {x2 + x + 9, {0, 0, 6x + 10, 0, 0, 7x + 4}, {0, 0, 0, 6x + 10, 7x + 4, 0}, {0, 7x + 4, 0, 0, 0, 6x + 10}} +- 137 (Circom definition) +- 139 (PAPER definition -> algebraic circuit) +- 147 (Groth16 parameters for 3-fac over BLS6-6): + Groth_16 − Param(R3. f ac_zk ) = (13, G1 [13], G2 [13], e(·, ·), (13, 15), (7v2 , 16v3 )) +- 148 (Groth16 via SnarkJS): + Statement to use BN254 / alt_bn128 in SnarkJS. +- 149 (CRS - Transformation of QAP into Common Reference String): + - choice of α, β, γ, δ, τ + #+begin_quote + | (27, 34), (26, 34), (38, 15), (13, 15), (33, 34) , O, (33, 9) | + CRS_G1 = | (33, 34), (26, 34), (38, 28), (27, 9) , (26, 34) | + + CRS_G2 = | (16v , 28v ), (37v , 27v ), (42v , 16v ), 7v , 16v ), (10v, 28v) | + #+end_quote +- 150 (SnarkJS setup for Groth16) +- 151 (Manual Groth16 prover phase over BLS6-6) +- 152 (Groth16 prover phase using SnarkJS) +- 153 (Manual Groth16 verification phase over BLS6-6) +- 154 (Groth16 verification phase in SnarkJS) +- 155 (Manual proof simulation trapdoor) +- 156 (Mention for SnarkJS simulation not implemented) + From e9fe2046414c6f3b3135f3d9d5c7aecf953e8b56 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 15:43:03 +0200 Subject: [PATCH 21/49] [examples] clean up outputs of code blocks --- examples/groth16_prover.org | 52 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/examples/groth16_prover.org b/examples/groth16_prover.org index 81de36cd9..b5eeb27b6 100644 --- a/examples/groth16_prover.org +++ b/examples/groth16_prover.org @@ -60,23 +60,26 @@ component main = three_fac() ; If we save this as ~three_fac.circom~ we can use Circom to generate the corresponding R1CS by: -#+begin_src sh :exports both +#+begin_src sh :results code :exports both +cd groth16_files circom three_fac.circom --r1cs --wasm --sym #+end_src #+RESULTS: -| template | instances: | 2 | -| non-linear | constraints: | 2 | -| linear | constraints: | 0 | -| public | inputs: | 0 | -| private | inputs: | 3 | -| public | outputs: | 1 | -| wires: | 6 | | -| labels: | 11 | | -| Written | successfully: | ./three_fac.r1cs | -| Written | successfully: | ./three_fac.sym | -| Written | successfully: | ./three_fac_js/three_fac.wasm | -| Everything | went | okay | +#+begin_src sh +template instances: 2 +non-linear constraints: 2 +linear constraints: 0 +public inputs: 0 +private inputs: 3 +public outputs: 1 +wires: 6 +labels: 11 +Written successfully: ./three_fac.r1cs +Written successfully: ./three_fac.sym +Written successfully: ./three_fac_js/three_fac.wasm +Everything went okay +#+end_src This produces 3 output files, a ~.r1cs~ file and a ~.sym~ file. Importantly, it also generates a new directory (in our case @@ -86,20 +89,20 @@ used to generate a ~.wtns~ file from some inputs used in the proof and ~witness_calculator.js~ containing most of the actual logic. Using SnarkJS we can print information about the R1CS file: -#+begin_src sh :results drawer :exports both +#+begin_src sh :results code :exports both snarkjs r1cs info groth16_files/three_fac.r1cs #+end_src #+RESULTS: -:results: -[INFO] snarkJS: Curve: bn-128 -[INFO] snarkJS: # of Wires: 6 -[INFO] snarkJS: # of Constraints: 2 -[INFO] snarkJS: # of Private Inputs: 3 -[INFO] snarkJS: # of Public Inputs: 0 -[INFO] snarkJS: # of Labels: 11 -[INFO] snarkJS: # of Outputs: 1 -:end: +#+begin_src sh +[INFO] snarkJS: Curve: bn-128 +[INFO] snarkJS: # of Wires: 6 +[INFO] snarkJS: # of Constraints: 2 +[INFO] snarkJS: # of Private Inputs: 3 +[INFO] snarkJS: # of Public Inputs: 0 +[INFO] snarkJS: # of Labels: 11 +[INFO] snarkJS: # of Outputs: 1 +#+end_src where we see the number of produced wires the corresponding algebraic circuit has, the number of constraints in the R1CS as well as the @@ -301,7 +304,7 @@ echo "C_p#16 = ", C_p.toHex() echo "C_p#10 = ", C_p.toDecimal() #+end_src -:RESULTS: +#+begin_src A_p#16 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( x: 0x0c37654828f4fa92099b6e6bc3ef1e233688e29775ad84a587e3e0a6b94734f5, y: 0x2eef49d5d85978033554eeadf6a3af464fd201d8c6687fdb51fc7f1077622d49 @@ -337,6 +340,7 @@ C_p#10 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( y: 03285628268350284309020370879913195676683658549907819489051412512121524349470 ) :END: +#+end_src As we can see when comparing the decimal outputs, the numbers match. From e92471fdb920fd56dd72442b91bfa33ec5efe9d2 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 15:55:51 +0200 Subject: [PATCH 22/49] remove left over `:END:` --- examples/groth16_prover.org | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/groth16_prover.org b/examples/groth16_prover.org index b5eeb27b6..8549d54dc 100644 --- a/examples/groth16_prover.org +++ b/examples/groth16_prover.org @@ -339,7 +339,6 @@ C_p#10 = EC_ShortW_Jac[Fp[BN254_Snarks], G1]( x: 12524785304069290509448413767699932740828656355192528146073107099016111123885, y: 03285628268350284309020370879913195676683658549907819489051412512121524349470 ) -:END: #+end_src As we can see when comparing the decimal outputs, the numbers match. From 020e88b9267ba773b16ff9145e72c5862e3d418e Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 17:19:27 +0200 Subject: [PATCH 23/49] remove old echoes --- .../proof_systems/constraint_systems/wtns_binary_parser.nim | 2 -- .../proof_systems/constraint_systems/zkey_binary_parser.nim | 4 ---- constantine/proof_systems/groth16_utils.nim | 1 - 3 files changed, 7 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index f08aa91e3..5e289e797 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -165,8 +165,6 @@ proc parseSection(f: File, wtns: var WtnsBin): Section = var kind: WtnsSectionKind var size: uint64 doAssert f.parseSectionKind(kind), "Failed to read section type in section " - echo "Got section kind::: ", kind - doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " result = initSection(kHeader, size) diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index b46e8bbff..e16aa6861 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -320,7 +320,6 @@ proc parseDataSection(f: File, d: var DataSection, sectionSize: uint64, elemSize proc parseDatasection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: var ZkeyBin): bool = let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme - echo "Parsing data section: ", kind case kind of kIC: ?f.parseDataSection(s.ic, size, 2 * g16h.n8q) of kA: ?f.parseDataSection(s.a, size, 2 * g16h.n8q) @@ -345,7 +344,6 @@ proc parseCoefficients(f: File, s: var Coefficients_b, zkey: ZkeyBin): bool = let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme s.cs = newSeq[Coefficient_b](s.num) for i in 0 ..< s.num: # parse coefficients - echo "Parsing coefficient with : ", g16h.n8r, " bytes" ?f.parseCoefficient(s.cs[i], g16h.n8r) result = true @@ -373,8 +371,6 @@ proc parseSection(f: File, zkey: var ZkeyBin): Section = var kind: ZkeySectionKind var size: uint64 doAssert f.parseSectionKind(kind), "Failed to read section type in section " - echo "Got section kind::: ", kind - doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " result = initSection(kHeader, size) diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index ff916a64f..e4a2bbffb 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -30,7 +30,6 @@ proc toEcG1*[Name: static Algebra](s: seq[byte]): EC_ShortW_Aff[Fp[Name], G1] = let y = toFp[Name](s[32 .. ^1]) result.x = x result.y = y - echo result.toHex() if not bool(result.isNeutral()): doAssert isOnCurve(result.x, result.y, G1).bool, "Input point is not on curve!" From f79152d24a451a6468aed97a12ff4ed38b73ff7c Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 17:19:33 +0200 Subject: [PATCH 24/49] export EC related modules from Groth16 --- constantine/proof_systems/manual_groth16.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/constantine/proof_systems/manual_groth16.nim b/constantine/proof_systems/manual_groth16.nim index 7c4de9333..e807aa799 100644 --- a/constantine/proof_systems/manual_groth16.nim +++ b/constantine/proof_systems/manual_groth16.nim @@ -18,7 +18,9 @@ import ./groth16_utils # Export so users can parse files export r1cs_circom_parser, zkey_binary_parser, wtns_binary_parser export groth16_utils -export arithmetic, extension_fields, abstractions, io_bigints, io_fields, io_ec, io_extfields +export arithmetic, extension_fields, abstractions, + io_bigints, io_fields, io_ec, io_extfields, + ec_shortweierstrass_affine, ec_shortweierstrass_jacobian type Groth16Prover*[Name: static Algebra] = object From 3d7eade4bdebb9c25fb0a8887ba56b18a9ea05bc Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 17:19:44 +0200 Subject: [PATCH 25/49] update tangled example Nim code --- examples/groth16_proof_example.nim | 8 -------- 1 file changed, 8 deletions(-) diff --git a/examples/groth16_proof_example.nim b/examples/groth16_proof_example.nim index 630cc6a3c..ebded9a53 100644 --- a/examples/groth16_proof_example.nim +++ b/examples/groth16_proof_example.nim @@ -1,13 +1,5 @@ import constantine/proof_systems/manual_groth16, constantine/named/algebras -#import ../math/[arithmetic, extension_fields], -# ../math/io/[io_bigints, io_fields, io_ec, io_extfields], -# ../platforms/abstractions, -# ../named/[algebras, properties_fields, properties_curves], -# ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], -# ../named/zoo_generators, -# ../csprngs/sysrand -#import ./groth16_utils const T = BN254_Snarks From 6a43160e3e577c702f28a10e145382ddaa1cfaba Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 17:19:57 +0200 Subject: [PATCH 26/49] [io_fields] annotate `fromDecimal` with raises, pop `raises: []` before --- constantine/math/io/io_fields.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/constantine/math/io/io_fields.nim b/constantine/math/io/io_fields.nim index c28f2567e..e42ed1d5e 100644 --- a/constantine/math/io/io_fields.nim +++ b/constantine/math/io/io_fields.nim @@ -116,7 +116,9 @@ func toDecimal*(f: FF): string = ## This function is NOT constant-time at the moment. f.toBig().toDecimal() -func fromDecimal*(dst: var FF, decimalString: string) = +{.pop.} # `fromDecimal` can raise `ValueError` + +func fromDecimal*(dst: var FF, decimalString: string) {.raises: [ValueError].} = ## Convert a decimal string. The input must be packed ## with no spaces or underscores. ## This assumes that bits and decimal length are **public.** @@ -136,7 +138,7 @@ func fromDecimal*(dst: var FF, decimalString: string) = let raw {.noinit.} = fromDecimal(dst.mres.typeof, decimalString) dst.fromBig(raw) -func fromDecimal*(T: type FF, hexString: string): T {.noInit.}= +func fromDecimal*(T: type FF, hexString: string): T {.raises: [ValueError], noInit.}= ## Convert a decimal string. The input must be packed ## with no spaces or underscores. ## This assumes that bits and decimal length are **public.** From dbab52b94e09e46e7aa3d281e5ee27e7d6d372c8 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 15 Aug 2024 17:28:33 +0200 Subject: [PATCH 27/49] [tests] add groth16 prover test case, binary files for test --- .../groth16_files/three_fac.r1cs | Bin 0 -> 400 bytes .../groth16_files/three_fac_final.zkey | Bin 0 -> 4148 bytes .../proof_systems/groth16_files/witness.wtns | Bin 0 -> 268 bytes tests/proof_systems/t_groth16_prover.nim | 96 ++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 tests/proof_systems/groth16_files/three_fac.r1cs create mode 100644 tests/proof_systems/groth16_files/three_fac_final.zkey create mode 100644 tests/proof_systems/groth16_files/witness.wtns create mode 100644 tests/proof_systems/t_groth16_prover.nim diff --git a/tests/proof_systems/groth16_files/three_fac.r1cs b/tests/proof_systems/groth16_files/three_fac.r1cs new file mode 100644 index 0000000000000000000000000000000000000000..8b4aaef9d99667ca28e34c2cb62f1a9eadcfe705 GIT binary patch literal 400 zcmXRiOfF_*U|?VdVkRK|0K^~w5(Dvp;KSsv51l6#?5y;7VXP4w(b%}nHDHJ40>g(z zeklepy&xJN0LimL6c99zSamEw^N6t*s)nGw4iNV-C_v1`tDX&_2CM@VcHCeVB+Nj3 Y11KLx!Q2TAQ*d~LF7`nHJUl$|OXA*ym&5`0 zjep6mu12d|+Lw8aoJYoML=B;5dp(>B99=6!(I8JZ zF4eLtI90jUR>giNy4{|zbl(@`9(OGH*!ES}6dcH)HUCa`3j*i+;#s=+jWs|Uy!5-g zo^C7*yr6P09>B&8ZpR;Qicawcgs4j}?=7ydL}sdTx17NcPo67C88ORSYnCH@UOexe z!#6fvQnHE@DZ29m@qx@GWEUF!>4N_FgG)mGX})z}ZoyIJMEJ!8k`-dZQh+5^EU4WJ zJLf$xWa>9GBeQFhL5g#IH(K{&&V~SLRVfv646!?ExrfQCi*FsJ&E*ZQ_RLsUDE8?pOk+F3bPpM zmIRqJc>|jXOI%F`=CpxL?jYJ4n4*zfU-Z?qn9Qa$#d}LL+rZBAuZ^&+=5Gtqdb|`j z$&uF1L}!XLjwk6BtxsSB`~ZT>UW&LR4uzO*e%)7}{Ggc_ghs7HLDFahpF^oQKu&x2 z(Rj5BZT?bpks~YW_{{4(Cw%)yKY6yXkM!IS@7}O{w1U7mA8jWAHH9o5zUroz|D8ob zo0pEVkFBQUzUKseOA)2x7oo9M4xdS&hMDHRa<2&?h-$V(Z62<9; z5+|BV;ClQ|;N)ZaXjwrU$j4g?y8Glwjl|UKY@bk253yj3*@iV5sU`e#7#=R4XKtLe zDqAhA8z$x(_0S8w*oZbg*=Y3#rOMjj^<$tv)QGbu`kQ`nwz$~-j^CeB|68`WSaH7p z$}cX?h@T0bUaMN)weRq~iv~^UmklZclsF#gP((;{O4C$V%q`Powzi~|S{r{Al)hjp z<7VkHw;w#`&iOD&?cX(3vVAg1-aOM~D!&vSl#}ft zl^++ZAW53Pqq{A~-N;_vCd_;kRp{L>Uja1L-H`agd83NXIP zml2_vW=EM^^9IHyi6AxN#7uXYD7!Zmd&hWGC)%FQInTFPptFZAvHw1$~_Ng<=OnvM}+l}i^f%*QT zOTm%MEUC||P6<)!LclaX4~7v&K2Tg?m4Jvdyez;$j%WvN{OxAA(%V!$hbfIZSdw8S zL-*ue@k7WckPa+g++Sy-ic!t!vC?h7D{!BGU%*!mBtG-pnLm2CSZ=Mq-L8yI6Zsa4 zwScBmaUg}>Uu56p2krJ{mG+jJQ>RZ~C$iSGT3DRO&WkEISF%xfZ>2#<`>P8;iVNW9 z8L^DFyy*F^RSEQB?8BsoY)y~6plDuJ)@tiWu>nD!8GVo~lR1~>4H9q6cLQl=$BsrJ zZ00Qu{D5~FoxE)qWxQRLykAOe+=|yiqy4=Cp5?q&kjwg3H9HavG~WZfq2ZRuoBfdq zdRfwOQ|D1v^0W&{nsNcD#CzQt1+yy2jd39zOXFocgr;PerENo^3#~ z|5%G)tC?WNpkT%mlCxvIkDg(u*Z8HAegbN}tJik-GwX@p01pxuCxl*&G@6q#;GGFE zH`Aw{CP~!+j=BwUuIX{OEs*J}>OkUL{D0J*hIwKWkZW%bJwHl2DKAMc$5Q}ABbRA= zC8(M)C74g2C1|stSVyz*P&ZKGaeF+pZInLQuED=|r5C<`e_Xk3g&E36A-2Qnp4z`- zp*0D?pg|BdhUfsbtLYI93Z_v+kz<7UHLh|_aF^c-X@vuH91>iGx~S(8>O6hx-(vJh{C%Aq*M=j<~X zs9N3vvgKdI;$v9=JP0?eeDjtFF}lNo&3l>|1!SQaxUMe69D#3e?wQXR1s*yvcC6~k zvRl1a`U12ap1Ik&1orkzkn9oBK^!no+<4+u~pnBFyMo5h1#{ffsZ8J@qB+Eb4*J;@O+dG_s{7pb|Ev5-AU9iLv_8IA=u zu@HOF`xzu(IZEuvYsA{WEOFl0l)RgkfX_8E!Gn=vbtc0gb3#w zgq>4t8T`POdwgxG?-qw)VOqvf9Y(fVQHXLK3A(xTIR2)H&{Bfp3Lxh$4X?IubNnsR zDS*;eey*`)EETl!UZGPt47u&+a$iraM#ipt6y5AXxnE5v$EWEL4fcKd@%Oi>6z)j{ zNI}uaFko@)#)oBf&6C&L{ixdQ6s9y23`L40oI`-1LQ$>(r4?ue_IhdJwWVZWH8Q{Q zfbu*}Ld>@_C}ZLEYR2(vP?UfBXjov?!IlFooh?t9irAd>O}+*V{|4r;0}h43VV+*a zQ@s90v@)xdA65qn`P5RJ%qo|s0N!u_8TN_f*n4lhK?}r`N?ac1djW>6#n@2m7IrhT z12cHFUTT57G|A}HXt7QQ-Ahm9TkBpNjN5>@aMhuMJGooCf)(wpZ9UwD Q1Y`hw0zx8U5>m2%0maa=9smFU literal 0 HcmV?d00001 diff --git a/tests/proof_systems/groth16_files/witness.wtns b/tests/proof_systems/groth16_files/witness.wtns new file mode 100644 index 0000000000000000000000000000000000000000..b49c6d012e43033aef37c1a15774d5652c696dd2 GIT binary patch literal 268 zcmXRf$tz}JU|;}YMj+MzVh~UOVj%y+6i^mt*c5gXCixXm?ShvovqheduV z25dkzAbkgb7z9B2@qx=c>wesJ@+z}XOsKe5|A6_vcuYe7IdzpkYj=((B Date: Tue, 20 Aug 2024 12:38:48 +0200 Subject: [PATCH 30/49] [tests] add `.zkey` parser test case --- tests/proof_systems/t_zkey_parser.nim | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/proof_systems/t_zkey_parser.nim diff --git a/tests/proof_systems/t_zkey_parser.nim b/tests/proof_systems/t_zkey_parser.nim new file mode 100644 index 000000000..2d8e867dc --- /dev/null +++ b/tests/proof_systems/t_zkey_parser.nim @@ -0,0 +1,56 @@ +import + std/[os, unittest, strutils, json], + constantine/proof_systems/constraint_systems/zkey_binary_parser, + constantine/named/algebras, + constantine/platforms/abstractions, + constantine/math/io/[io_bigints, io_fields], + constantine/math/[arithmetic, extension_fields], + constantine/proof_systems/groth16_utils + +## The two procs below are to serialize `ZkeyBin` and `Zkey` to JSON. +proc `%`(c: char): JsonNode = % ($c) +proc `%`(c: SecretWord): JsonNode = % (c.uint64) + +const UpdateTestVectors = false +const RawVec = "groth16_files/t_zkey_bin.json" +const TypedVec = "groth16_files/t_zkey.json" + +const TestDir = currentSourcePath.rsplit(DirSep, 1)[0] +suite "Zkey (.zkey) binary file parser": + + + ## NOTE: We perform the check of whether we parse the `.zkey` binary file correctly + ## by writing the Zkey types as JSON data. Ideally, we would explicitly check each + ## field, but given the large number of fields, it would make for a pretty big + ## test case. Given that the parsed data has been utilized for a successful Groth16 + ## proof, we consider the stored JSON files as 'correct'. + test "Parse Moonmath 3-factorization `.zkey` file": + const path = TestDir / "groth16_files/three_fac_final.zkey" + let zkey = parseZkeyFile(path) + + let zj = % zkey + + when UpdateTestVectors: + writeFile(RawVec, zj.pretty()) + block CheckRaw: + check zkey.magic == ['z', 'k', 'e', 'y'] + check zkey.version == 1 + check zkey.header().proverType == 1 + check zkey.numberSections == 10 + + # read expected test vector + let exp = RawVec.readFile().parseJson() + check zj == exp + + block CheckTyped: + const T = BN254_Snarks + # convert to 'typed' (unmarshalled) type + let zkey = zkey.toZkey[:T]() + + let zj = % zkey + when UpdateTestVectors: + writeFile(TypedVec, zj.pretty()) + + # read expected test vector + let exp = TypedVec.readFile().parseJson() + check zj == exp From 4a12fe703eb43c1db7d37c5e83a92735e560070e Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 20 Aug 2024 12:39:03 +0200 Subject: [PATCH 31/49] [tests] add expected zkey test files as JSON data --- tests/proof_systems/groth16_files/t_zkey.json | 1257 +++++++ .../groth16_files/t_zkey_bin.json | 3282 +++++++++++++++++ 2 files changed, 4539 insertions(+) create mode 100644 tests/proof_systems/groth16_files/t_zkey.json create mode 100644 tests/proof_systems/groth16_files/t_zkey_bin.json diff --git a/tests/proof_systems/groth16_files/t_zkey.json b/tests/proof_systems/groth16_files/t_zkey.json new file mode 100644 index 000000000..9c170f3f4 --- /dev/null +++ b/tests/proof_systems/groth16_files/t_zkey.json @@ -0,0 +1,1257 @@ +{ + "version": 1, + "header": { + "proverType": 1 + }, + "g16h": { + "n8q": 32, + "q": [ + 71, + 253, + 124, + 216, + 22, + 140, + 32, + 60, + 141, + 202, + 113, + 104, + 145, + 106, + 129, + 151, + 93, + 88, + 129, + 129, + 182, + 69, + 80, + 184, + 41, + 160, + 49, + 225, + 114, + 78, + 100, + 48 + ], + "n8r": 32, + "r": [ + 1, + 0, + 0, + 240, + 147, + 245, + 225, + 67, + 145, + 112, + 185, + 121, + 72, + 232, + 51, + 40, + 93, + 88, + 129, + 129, + 182, + 69, + 80, + 184, + 41, + 160, + 49, + 225, + 114, + 78, + 100, + 48 + ], + "nVars": 6, + "nPublic": 1, + "domainSize": 4, + "alpha1": { + "x": { + "mres": { + "limbs": [ + 13925964088572246216, + 13742589492058863780, + 9476940806093235905, + 3362611766509840827 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 17481104537668069806, + 13950129170139171565, + 9579658453758972571, + 607686539175759096 + ] + } + } + }, + "beta1": { + "x": { + "mres": { + "limbs": [ + 14764516605348937795, + 9591755601364069681, + 14464396726792719788, + 2282336362458004519 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 16240601560751194657, + 12175426767587956416, + 16774016708564877740, + 2512680640774327453 + ] + } + } + }, + "beta2": { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 3270322030233528447, + 9016609499626425979, + 18106800030602388967, + 116589489467730433 + ] + } + }, + { + "mres": { + "limbs": [ + 7147804817778042924, + 13926681233934608099, + 2593579544344806394, + 125737815924096011 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 5042516886431319744, + 7330878682177452837, + 17764006822939567364, + 1100800799847715895 + ] + } + }, + { + "mres": { + "limbs": [ + 5054136446061530389, + 8582722777749975593, + 9606551017233391694, + 2051338976594960443 + ] + } + } + ] + } + }, + "gamma2": { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 10269251484633538598, + 15918845024527909234, + 18138289588161026783, + 1825990028691918907 + ] + } + }, + { + "mres": { + "limbs": [ + 12660871435976991040, + 6936631231174072516, + 714191060563144582, + 1512910971262892907 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 7034053747528165878, + 18338607757778656120, + 18419188534790028798, + 2953656481336934918 + ] + } + }, + { + "mres": { + "limbs": [ + 7208393106848765678, + 15877432936589245627, + 6195041853444001910, + 983087530859390082 + ] + } + } + ] + } + }, + "delta1": { + "x": { + "mres": { + "limbs": [ + 10002989469976289783, + 16477875574839359909, + 7857093606726690942, + 253235042624443816 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 12706341286799195498, + 18332677950483261025, + 4056129645201674314, + 2709618686330962747 + ] + } + } + }, + "delta2": { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 10780097336817227228, + 15308435357483846446, + 7827071303385416315, + 2976043425012853800 + ] + } + }, + { + "mres": { + "limbs": [ + 9926601593583141772, + 10478646357770173204, + 1886462824504761957, + 1113151453811462486 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 5681511216328213686, + 4772598523326543119, + 10918010982668516951, + 1146120876580835765 + ] + } + }, + { + "mres": { + "limbs": [ + 17633534535112326590, + 9010073426057181659, + 17827518104323058273, + 84099701317197459 + ] + } + } + ] + } + } + }, + "ic": [ + { + "x": { + "mres": { + "limbs": [ + 8498507250225182879, + 11414283414803093306, + 86313205369982859, + 1327344531303714366 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 17778271140205288861, + 14410647442925908475, + 2939680923049302282, + 3013237186062503147 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 144182305798471186, + 7063012362356384839, + 17960025875269118135, + 3398549968082205082 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 4256052102704685329, + 9656698010830347098, + 329749830419440050, + 3465742425856225961 + ] + } + } + } + ], + "coeffs": { + "num": 6, + "cs": [ + { + "matrix": 0, + "section": 0, + "index": 2, + "value": { + "mres": { + "limbs": [ + 2893861064349225562, + 15291318972085769902, + 3172436813243865047, + 3336461168475855748 + ] + } + } + }, + { + "matrix": 1, + "section": 0, + "index": 3, + "value": { + "mres": { + "limbs": [ + 1997599621687373223, + 6052339484930628067, + 10108755138030829701, + 150537098327114917 + ] + } + } + }, + { + "matrix": 0, + "section": 1, + "index": 5, + "value": { + "mres": { + "limbs": [ + 2893861064349225562, + 15291318972085769902, + 3172436813243865047, + 3336461168475855748 + ] + } + } + }, + { + "matrix": 1, + "section": 1, + "index": 4, + "value": { + "mres": { + "limbs": [ + 1997599621687373223, + 6052339484930628067, + 10108755138030829701, + 150537098327114917 + ] + } + } + }, + { + "matrix": 0, + "section": 2, + "index": 0, + "value": { + "mres": { + "limbs": [ + 1997599621687373223, + 6052339484930628067, + 10108755138030829701, + 150537098327114917 + ] + } + } + }, + { + "matrix": 0, + "section": 3, + "index": 1, + "value": { + "mres": { + "limbs": [ + 1997599621687373223, + 6052339484930628067, + 10108755138030829701, + 150537098327114917 + ] + } + } + } + ] + }, + "A": [ + { + "x": { + "mres": { + "limbs": [ + 8411991738497574394, + 4812842035064521053, + 3855093717410988801, + 1287704766722423230 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 17534872512909326999, + 13770361305464453420, + 15269905503833711964, + 1931340478843024912 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 3555393664030893055, + 2568631973890133638, + 6059213695816936250, + 2841044097474115818 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 18080596052961610625, + 640201565314638412, + 4330941541260809362, + 1309194338023650263 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 8640835477903764226, + 7170250978333027317, + 1669000963016125122, + 2642131684488230576 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 13120564344670812193, + 4283057503361257490, + 855506031648810442, + 1790915358084121621 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 8461714475464199279, + 1628129506834983000, + 14604041186392693521, + 1514287081098762343 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 14933225774167841338, + 4227186455304672631, + 9056987616518665933, + 2530337990259817207 + ] + } + } + } + ], + "B1": [ + { + "x": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 8640835477903764226, + 7170250978333027317, + 1669000963016125122, + 2642131684488230576 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 9658796600318395686, + 6634066641116625530, + 12425685919625884307, + 1696082908718849044 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 8461714475464199279, + 1628129506834983000, + 14604041186392693521, + 1514287081098762343 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 7846135170821366541, + 6689937689173210389, + 4224204334756028816, + 956660276543153458 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + } + ], + "B2": [ + { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + } + }, + { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + } + }, + { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + } + }, + { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 2221700885755314967, + 3425978242931448249, + 8171574967084206717, + 1738847330961988603 + ] + } + }, + { + "mres": { + "limbs": [ + 11502417555104571261, + 924347463091524855, + 12612908236985029529, + 3139890662700272006 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 10794151037613322987, + 15417924285964247925, + 16373442397629755450, + 1792105613104675116 + ] + } + }, + { + "mres": { + "limbs": [ + 8676715419424595653, + 1880676684100826781, + 3263342836038281103, + 2961060771967373583 + ] + } + } + ] + } + }, + { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 5073919794795742742, + 6361026632926853030, + 7445452725032007412, + 1561155376839150292 + ] + } + }, + { + "mres": { + "limbs": [ + 1139168740997190592, + 5143868095894396947, + 6754363017172502520, + 1602846249401879083 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 11802952941633001010, + 5481908123034181950, + 1050247317803748989, + 3041787918381055263 + ] + } + }, + { + "mres": { + "limbs": [ + 6675958238158383638, + 6939027800843457296, + 18275187947023293659, + 1298244372613128771 + ] + } + } + ] + } + }, + { + "x": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + }, + "y": { + "coords": [ + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + }, + { + "mres": { + "limbs": [ + 0, + 0, + 0, + 0 + ] + } + } + ] + } + } + ], + "C": [ + { + "x": { + "mres": { + "limbs": [ + 4880452224974455006, + 12483256698092434454, + 16183853057546404034, + 1466982721751600570 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 519123567512150905, + 13648186404792563826, + 8575498277460323763, + 2929308687446985433 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 11776177061220625087, + 11144007132154920161, + 14832512964782604742, + 2743194658126172026 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 11868980184996092981, + 7770272586030593725, + 5901181197870207239, + 3301669830076875222 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 8072160774775660367, + 16049166721152470622, + 1427861074745032064, + 1743675877608078668 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 15537118409330861305, + 12808035235080574992, + 5774621647102064814, + 311370515709796437 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 9972454936968116998, + 9396835638540614424, + 15571406619042388759, + 820332184817920381 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 9145492366232960641, + 7659465626739688343, + 12362538444863847411, + 1133623199045677193 + ] + } + } + } + ], + "H": [ + { + "x": { + "mres": { + "limbs": [ + 1691797751783121018, + 9111041891540600823, + 5200379746596341433, + 482333683196747972 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 14064129055996645321, + 3313242041195738061, + 12698053057629697561, + 2257189183036650753 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 9665656393038420845, + 18406351292205921899, + 4173835129935986056, + 75303207305036905 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 13118481880823267632, + 10957880156592055544, + 1616085079919492922, + 1499623060997469833 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 5099446712988230797, + 4186623324024048365, + 14379833201355983657, + 1639864572092178152 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 10602679100874508323, + 18073232837032653440, + 1405969893897065789, + 990083763231913698 + ] + } + } + }, + { + "x": { + "mres": { + "limbs": [ + 12136017249349928552, + 12943707575502204642, + 6556358596293514448, + 3406940363828159493 + ] + } + }, + "y": { + "mres": { + "limbs": [ + 15023088725600681953, + 10270132635661047970, + 12703004539836373783, + 251945972054357677 + ] + } + } + } + ], + "contr": { + "hash": [ + 208, + 4, + 157, + 173, + 108, + 62, + 105, + 70, + 107, + 29, + 159, + 18, + 46, + 119, + 79, + 103, + 136, + 108, + 45, + 191, + 16, + 216, + 136, + 176, + 112, + 116, + 177, + 201, + 149, + 135, + 77, + 148, + 149, + 104, + 199, + 58, + 20, + 127, + 255, + 144, + 83, + 193, + 133, + 0, + 233, + 97, + 1, + 132, + 211, + 186, + 141, + 158, + 96, + 60, + 14, + 187, + 185, + 120, + 21, + 223, + 228, + 203, + 39, + 57 + ], + "num": 2 + } +} \ No newline at end of file diff --git a/tests/proof_systems/groth16_files/t_zkey_bin.json b/tests/proof_systems/groth16_files/t_zkey_bin.json new file mode 100644 index 000000000..32c9f4310 --- /dev/null +++ b/tests/proof_systems/groth16_files/t_zkey_bin.json @@ -0,0 +1,3282 @@ +{ + "magic": [ + "z", + "k", + "e", + "y" + ], + "version": 1, + "numberSections": 10, + "sections": [ + { + "size": 4, + "sectionType": "kHeader", + "header": { + "proverType": 1 + } + }, + { + "size": 660, + "sectionType": "kGroth16Header", + "g16h": { + "n8q": 32, + "q": [ + 71, + 253, + 124, + 216, + 22, + 140, + 32, + 60, + 141, + 202, + 113, + 104, + 145, + 106, + 129, + 151, + 93, + 88, + 129, + 129, + 182, + 69, + 80, + 184, + 41, + 160, + 49, + 225, + 114, + 78, + 100, + 48 + ], + "n8r": 32, + "r": [ + 1, + 0, + 0, + 240, + 147, + 245, + 225, + 67, + 145, + 112, + 185, + 121, + 72, + 232, + 51, + 40, + 93, + 88, + 129, + 129, + 182, + 69, + 80, + 184, + 41, + 160, + 49, + 225, + 114, + 78, + 100, + 48 + ], + "nVars": 6, + "nPublic": 1, + "domainSize": 4, + "alpha1": [ + 200, + 248, + 49, + 62, + 142, + 246, + 66, + 193, + 164, + 56, + 47, + 232, + 84, + 124, + 183, + 190, + 193, + 102, + 151, + 192, + 115, + 219, + 132, + 131, + 187, + 41, + 94, + 244, + 149, + 101, + 170, + 46, + 174, + 169, + 44, + 73, + 245, + 91, + 153, + 242, + 237, + 202, + 245, + 188, + 145, + 208, + 152, + 193, + 155, + 18, + 223, + 197, + 155, + 200, + 241, + 132, + 248, + 140, + 91, + 14, + 176, + 239, + 110, + 8 + ], + "beta1": [ + 67, + 0, + 46, + 118, + 160, + 25, + 230, + 204, + 49, + 45, + 171, + 111, + 230, + 194, + 28, + 133, + 172, + 141, + 205, + 246, + 47, + 220, + 187, + 200, + 39, + 60, + 68, + 78, + 212, + 124, + 172, + 31, + 33, + 194, + 58, + 183, + 18, + 53, + 98, + 225, + 192, + 214, + 220, + 14, + 35, + 206, + 247, + 168, + 172, + 1, + 122, + 234, + 82, + 71, + 201, + 232, + 157, + 204, + 90, + 227, + 186, + 213, + 222, + 34 + ], + "beta2": [ + 127, + 40, + 52, + 65, + 143, + 132, + 98, + 45, + 123, + 78, + 165, + 95, + 145, + 110, + 33, + 125, + 231, + 101, + 22, + 163, + 177, + 70, + 72, + 251, + 1, + 170, + 68, + 224, + 133, + 53, + 158, + 1, + 44, + 104, + 124, + 111, + 156, + 26, + 50, + 99, + 227, + 102, + 197, + 168, + 203, + 130, + 69, + 193, + 250, + 67, + 100, + 173, + 218, + 62, + 254, + 35, + 11, + 60, + 253, + 156, + 224, + 181, + 190, + 1, + 192, + 198, + 165, + 77, + 100, + 158, + 250, + 69, + 37, + 187, + 171, + 30, + 82, + 131, + 188, + 101, + 4, + 165, + 237, + 116, + 22, + 110, + 134, + 246, + 55, + 148, + 99, + 195, + 126, + 212, + 70, + 15, + 21, + 77, + 218, + 217, + 81, + 230, + 35, + 70, + 41, + 242, + 112, + 165, + 228, + 244, + 27, + 119, + 78, + 32, + 221, + 162, + 64, + 83, + 81, + 133, + 59, + 32, + 219, + 69, + 238, + 209, + 119, + 28 + ], + "gamma2": [ + 38, + 32, + 188, + 2, + 209, + 181, + 131, + 142, + 114, + 1, + 123, + 73, + 53, + 25, + 235, + 220, + 223, + 26, + 129, + 151, + 71, + 38, + 184, + 251, + 59, + 80, + 150, + 175, + 65, + 56, + 87, + 25, + 64, + 97, + 76, + 168, + 125, + 115, + 180, + 175, + 196, + 216, + 2, + 88, + 90, + 221, + 67, + 96, + 134, + 47, + 160, + 82, + 252, + 80, + 233, + 9, + 107, + 123, + 234, + 58, + 131, + 240, + 254, + 20, + 246, + 233, + 107, + 136, + 157, + 250, + 157, + 97, + 120, + 155, + 158, + 245, + 151, + 210, + 127, + 254, + 254, + 125, + 27, + 35, + 98, + 26, + 158, + 255, + 6, + 66, + 158, + 174, + 235, + 126, + 253, + 40, + 238, + 86, + 24, + 199, + 86, + 91, + 9, + 100, + 187, + 60, + 125, + 50, + 34, + 249, + 87, + 220, + 118, + 16, + 53, + 51, + 190, + 53, + 249, + 85, + 130, + 100, + 253, + 147, + 230, + 160, + 164, + 13 + ], + "delta1": [ + 247, + 225, + 232, + 193, + 236, + 193, + 209, + 138, + 165, + 185, + 98, + 35, + 136, + 44, + 173, + 228, + 126, + 232, + 239, + 179, + 1, + 1, + 10, + 109, + 168, + 149, + 79, + 248, + 237, + 171, + 131, + 3, + 106, + 189, + 139, + 74, + 20, + 254, + 85, + 176, + 97, + 230, + 48, + 28, + 119, + 193, + 106, + 254, + 74, + 80, + 45, + 9, + 108, + 68, + 74, + 56, + 59, + 131, + 132, + 203, + 214, + 127, + 154, + 37 + ], + "delta2": [ + 220, + 21, + 32, + 149, + 105, + 153, + 154, + 149, + 46, + 203, + 218, + 157, + 217, + 124, + 114, + 212, + 123, + 2, + 241, + 125, + 225, + 87, + 159, + 108, + 40, + 44, + 7, + 48, + 186, + 7, + 77, + 41, + 140, + 131, + 89, + 84, + 142, + 95, + 194, + 137, + 20, + 251, + 212, + 25, + 88, + 161, + 107, + 145, + 101, + 30, + 150, + 168, + 241, + 15, + 46, + 26, + 86, + 81, + 164, + 121, + 89, + 181, + 114, + 15, + 182, + 4, + 164, + 108, + 89, + 200, + 216, + 78, + 15, + 181, + 110, + 22, + 18, + 173, + 59, + 66, + 87, + 62, + 197, + 131, + 36, + 145, + 132, + 151, + 181, + 145, + 66, + 102, + 221, + 214, + 231, + 15, + 190, + 253, + 243, + 179, + 59, + 230, + 182, + 244, + 219, + 57, + 83, + 40, + 11, + 54, + 10, + 125, + 97, + 106, + 5, + 251, + 66, + 17, + 104, + 247, + 147, + 94, + 186, + 150, + 59, + 200, + 42, + 1 + ] + } + }, + { + "size": 128, + "sectionType": "kIC", + "ic": { + "points": [ + [ + 159, + 248, + 192, + 42, + 81, + 195, + 240, + 117, + 58, + 151, + 248, + 80, + 74, + 174, + 103, + 158, + 139, + 207, + 128, + 41, + 103, + 165, + 50, + 1, + 62, + 78, + 234, + 146, + 209, + 172, + 107, + 18, + 157, + 221, + 131, + 160, + 104, + 27, + 185, + 246, + 251, + 113, + 253, + 154, + 127, + 231, + 252, + 199, + 10, + 153, + 88, + 115, + 57, + 216, + 203, + 40, + 235, + 204, + 64, + 131, + 65, + 43, + 209, + 41 + ], + [ + 18, + 190, + 3, + 16, + 11, + 61, + 0, + 2, + 71, + 228, + 113, + 118, + 82, + 220, + 4, + 98, + 183, + 104, + 61, + 148, + 96, + 212, + 62, + 249, + 154, + 121, + 244, + 142, + 48, + 19, + 42, + 47, + 17, + 97, + 68, + 127, + 214, + 136, + 16, + 59, + 90, + 155, + 195, + 110, + 173, + 123, + 3, + 134, + 178, + 97, + 229, + 66, + 185, + 129, + 147, + 4, + 169, + 50, + 41, + 192, + 95, + 202, + 24, + 48 + ] + ] + } + }, + { + "size": 268, + "sectionType": "kCoeffs", + "coeffs": { + "num": 6, + "cs": [ + { + "matrix": 0, + "section": 0, + "index": 2, + "value": [ + 90, + 146, + 222, + 65, + 78, + 15, + 41, + 40, + 174, + 22, + 93, + 150, + 150, + 173, + 53, + 212, + 215, + 215, + 197, + 45, + 121, + 194, + 6, + 44, + 132, + 91, + 227, + 97, + 193, + 125, + 77, + 46 + ] + }, + { + "matrix": 1, + "section": 0, + "index": 3, + "value": [ + 167, + 109, + 33, + 174, + 69, + 230, + 184, + 27, + 227, + 89, + 92, + 227, + 177, + 58, + 254, + 83, + 133, + 128, + 187, + 83, + 61, + 131, + 73, + 140, + 165, + 68, + 78, + 127, + 177, + 208, + 22, + 2 + ] + }, + { + "matrix": 0, + "section": 1, + "index": 5, + "value": [ + 90, + 146, + 222, + 65, + 78, + 15, + 41, + 40, + 174, + 22, + 93, + 150, + 150, + 173, + 53, + 212, + 215, + 215, + 197, + 45, + 121, + 194, + 6, + 44, + 132, + 91, + 227, + 97, + 193, + 125, + 77, + 46 + ] + }, + { + "matrix": 1, + "section": 1, + "index": 4, + "value": [ + 167, + 109, + 33, + 174, + 69, + 230, + 184, + 27, + 227, + 89, + 92, + 227, + 177, + 58, + 254, + 83, + 133, + 128, + 187, + 83, + 61, + 131, + 73, + 140, + 165, + 68, + 78, + 127, + 177, + 208, + 22, + 2 + ] + }, + { + "matrix": 0, + "section": 2, + "index": 0, + "value": [ + 167, + 109, + 33, + 174, + 69, + 230, + 184, + 27, + 227, + 89, + 92, + 227, + 177, + 58, + 254, + 83, + 133, + 128, + 187, + 83, + 61, + 131, + 73, + 140, + 165, + 68, + 78, + 127, + 177, + 208, + 22, + 2 + ] + }, + { + "matrix": 0, + "section": 3, + "index": 1, + "value": [ + 167, + 109, + 33, + 174, + 69, + 230, + 184, + 27, + 227, + 89, + 92, + 227, + 177, + 58, + 254, + 83, + 133, + 128, + 187, + 83, + 61, + 131, + 73, + 140, + 165, + 68, + 78, + 127, + 177, + 208, + 22, + 2 + ] + } + ] + } + }, + { + "size": 384, + "sectionType": "kA", + "a": { + "points": [ + [ + 250, + 37, + 171, + 226, + 234, + 101, + 189, + 116, + 93, + 193, + 22, + 212, + 84, + 166, + 202, + 66, + 1, + 67, + 31, + 146, + 79, + 11, + 128, + 53, + 190, + 57, + 17, + 172, + 169, + 216, + 222, + 17, + 151, + 186, + 184, + 145, + 165, + 97, + 88, + 243, + 44, + 57, + 254, + 24, + 166, + 38, + 26, + 191, + 92, + 101, + 202, + 253, + 38, + 154, + 233, + 211, + 16, + 118, + 197, + 205, + 238, + 127, + 205, + 26 + ], + [ + 255, + 99, + 60, + 61, + 179, + 75, + 87, + 49, + 134, + 142, + 110, + 202, + 43, + 157, + 165, + 35, + 58, + 171, + 89, + 213, + 192, + 166, + 22, + 84, + 234, + 56, + 42, + 221, + 141, + 106, + 109, + 39, + 129, + 75, + 27, + 168, + 80, + 46, + 235, + 250, + 76, + 98, + 58, + 133, + 238, + 115, + 226, + 8, + 146, + 84, + 213, + 173, + 98, + 152, + 26, + 60, + 215, + 51, + 218, + 99, + 80, + 49, + 43, + 18 + ], + [ + 2, + 235, + 11, + 7, + 22, + 106, + 234, + 119, + 245, + 235, + 239, + 113, + 70, + 217, + 129, + 99, + 194, + 106, + 92, + 122, + 136, + 123, + 41, + 23, + 176, + 182, + 185, + 183, + 196, + 188, + 170, + 36, + 33, + 120, + 194, + 120, + 193, + 155, + 21, + 182, + 18, + 80, + 107, + 80, + 27, + 122, + 112, + 59, + 202, + 165, + 161, + 255, + 51, + 94, + 223, + 11, + 21, + 68, + 101, + 141, + 7, + 156, + 218, + 24 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 111, + 12, + 211, + 188, + 123, + 12, + 110, + 117, + 88, + 40, + 59, + 7, + 41, + 71, + 152, + 22, + 17, + 91, + 28, + 96, + 21, + 250, + 171, + 202, + 103, + 216, + 218, + 196, + 19, + 212, + 3, + 21, + 58, + 90, + 251, + 211, + 184, + 121, + 61, + 207, + 119, + 21, + 199, + 59, + 175, + 251, + 169, + 58, + 205, + 246, + 180, + 81, + 64, + 226, + 176, + 125, + 247, + 186, + 28, + 239, + 254, + 144, + 29, + 35 + ] + ] + } + }, + { + "size": 384, + "sectionType": "kB1", + "b1": { + "points": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 2, + 235, + 11, + 7, + 22, + 106, + 234, + 119, + 245, + 235, + 239, + 113, + 70, + 217, + 129, + 99, + 194, + 106, + 92, + 122, + 136, + 123, + 41, + 23, + 176, + 182, + 185, + 183, + 196, + 188, + 170, + 36, + 38, + 133, + 186, + 95, + 85, + 240, + 10, + 134, + 122, + 122, + 6, + 24, + 118, + 240, + 16, + 92, + 147, + 178, + 223, + 129, + 130, + 231, + 112, + 172, + 20, + 92, + 204, + 83, + 107, + 178, + 137, + 23 + ], + [ + 111, + 12, + 211, + 188, + 123, + 12, + 110, + 117, + 88, + 40, + 59, + 7, + 41, + 71, + 152, + 22, + 17, + 91, + 28, + 96, + 21, + 250, + 171, + 202, + 103, + 216, + 218, + 196, + 19, + 212, + 3, + 21, + 13, + 163, + 129, + 4, + 94, + 18, + 227, + 108, + 21, + 181, + 170, + 44, + 226, + 110, + 215, + 92, + 144, + 97, + 204, + 47, + 118, + 99, + 159, + 58, + 50, + 229, + 20, + 242, + 115, + 189, + 70, + 13 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] + } + }, + { + "size": 768, + "sectionType": "kB2", + "b2": { + "points": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 23, + 195, + 157, + 60, + 47, + 17, + 213, + 30, + 185, + 89, + 37, + 114, + 15, + 133, + 139, + 47, + 125, + 74, + 135, + 64, + 35, + 68, + 103, + 113, + 251, + 27, + 160, + 72, + 111, + 160, + 33, + 24, + 125, + 159, + 57, + 238, + 210, + 203, + 160, + 159, + 247, + 148, + 154, + 202, + 30, + 241, + 211, + 12, + 153, + 83, + 23, + 42, + 55, + 13, + 10, + 175, + 134, + 201, + 208, + 255, + 235, + 33, + 147, + 43, + 235, + 50, + 219, + 200, + 45, + 135, + 204, + 149, + 117, + 99, + 196, + 78, + 115, + 120, + 247, + 213, + 58, + 176, + 10, + 69, + 28, + 39, + 58, + 227, + 44, + 121, + 22, + 101, + 143, + 214, + 222, + 24, + 197, + 30, + 245, + 231, + 179, + 226, + 105, + 120, + 157, + 118, + 48, + 221, + 122, + 129, + 25, + 26, + 143, + 167, + 96, + 250, + 4, + 185, + 73, + 45, + 15, + 145, + 117, + 111, + 21, + 205, + 23, + 41 + ], + [ + 22, + 134, + 159, + 171, + 43, + 47, + 106, + 70, + 166, + 119, + 103, + 62, + 5, + 232, + 70, + 88, + 244, + 54, + 127, + 67, + 206, + 143, + 83, + 103, + 212, + 74, + 176, + 59, + 139, + 86, + 170, + 21, + 192, + 211, + 93, + 50, + 239, + 35, + 207, + 15, + 19, + 32, + 64, + 163, + 199, + 176, + 98, + 71, + 248, + 71, + 154, + 194, + 96, + 81, + 188, + 93, + 43, + 138, + 42, + 237, + 43, + 116, + 62, + 22, + 50, + 214, + 175, + 106, + 42, + 131, + 204, + 163, + 62, + 225, + 199, + 188, + 100, + 166, + 19, + 76, + 125, + 206, + 255, + 255, + 94, + 58, + 147, + 14, + 31, + 125, + 45, + 229, + 254, + 153, + 54, + 42, + 22, + 234, + 197, + 152, + 165, + 196, + 165, + 92, + 16, + 147, + 213, + 51, + 5, + 97, + 76, + 96, + 219, + 220, + 210, + 153, + 156, + 130, + 158, + 253, + 67, + 98, + 65, + 229, + 96, + 74, + 4, + 18 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] + } + }, + { + "size": 256, + "sectionType": "kC", + "c": { + "points": [ + [ + 222, + 0, + 222, + 220, + 112, + 217, + 186, + 67, + 22, + 180, + 208, + 233, + 211, + 111, + 61, + 173, + 194, + 64, + 44, + 48, + 156, + 152, + 152, + 224, + 186, + 205, + 163, + 85, + 3, + 197, + 91, + 20, + 121, + 99, + 24, + 92, + 34, + 76, + 52, + 7, + 114, + 248, + 249, + 85, + 57, + 25, + 104, + 189, + 179, + 45, + 255, + 151, + 63, + 74, + 2, + 119, + 217, + 74, + 190, + 40, + 187, + 254, + 166, + 40 + ], + [ + 191, + 170, + 40, + 242, + 164, + 98, + 109, + 163, + 225, + 124, + 131, + 118, + 113, + 119, + 167, + 154, + 198, + 65, + 62, + 152, + 244, + 171, + 215, + 205, + 122, + 15, + 92, + 235, + 1, + 201, + 17, + 38, + 53, + 156, + 215, + 252, + 151, + 22, + 183, + 164, + 189, + 54, + 79, + 146, + 191, + 141, + 213, + 107, + 7, + 145, + 68, + 158, + 8, + 53, + 229, + 81, + 214, + 65, + 91, + 171, + 55, + 227, + 209, + 45 + ], + [ + 79, + 95, + 88, + 222, + 116, + 19, + 6, + 112, + 94, + 78, + 80, + 203, + 25, + 24, + 186, + 222, + 128, + 253, + 200, + 20, + 21, + 200, + 208, + 19, + 76, + 61, + 63, + 232, + 248, + 199, + 50, + 24, + 249, + 36, + 89, + 197, + 223, + 238, + 158, + 215, + 16, + 24, + 170, + 185, + 47, + 72, + 191, + 177, + 174, + 184, + 204, + 243, + 202, + 147, + 35, + 80, + 85, + 160, + 105, + 150, + 213, + 53, + 82, + 4 + ], + [ + 6, + 59, + 147, + 200, + 237, + 70, + 101, + 138, + 24, + 187, + 241, + 7, + 58, + 68, + 104, + 130, + 23, + 27, + 48, + 181, + 209, + 191, + 24, + 216, + 125, + 105, + 228, + 153, + 198, + 103, + 98, + 11, + 129, + 190, + 92, + 41, + 218, + 80, + 235, + 126, + 151, + 67, + 34, + 235, + 103, + 227, + 75, + 106, + 243, + 59, + 56, + 80, + 55, + 143, + 144, + 171, + 137, + 112, + 66, + 24, + 75, + 112, + 187, + 15 + ] + ] + } + }, + { + "size": 256, + "sectionType": "kH", + "h": { + "points": [ + [ + 122, + 96, + 214, + 122, + 23, + 121, + 122, + 23, + 247, + 7, + 251, + 246, + 83, + 236, + 112, + 126, + 185, + 198, + 2, + 165, + 211, + 117, + 43, + 72, + 196, + 224, + 37, + 234, + 237, + 151, + 177, + 6, + 201, + 15, + 238, + 137, + 220, + 210, + 45, + 195, + 205, + 179, + 92, + 8, + 21, + 0, + 251, + 45, + 25, + 182, + 20, + 143, + 250, + 139, + 56, + 176, + 1, + 245, + 192, + 85, + 154, + 37, + 83, + 31 + ], + [ + 109, + 219, + 9, + 84, + 71, + 79, + 35, + 134, + 107, + 118, + 252, + 174, + 250, + 126, + 112, + 255, + 136, + 125, + 225, + 206, + 239, + 112, + 236, + 57, + 105, + 68, + 221, + 57, + 222, + 135, + 11, + 1, + 48, + 129, + 222, + 18, + 196, + 53, + 14, + 182, + 248, + 164, + 248, + 240, + 241, + 53, + 18, + 152, + 58, + 255, + 104, + 91, + 209, + 124, + 109, + 22, + 137, + 246, + 188, + 135, + 58, + 187, + 207, + 20 + ], + [ + 141, + 100, + 178, + 118, + 196, + 223, + 196, + 70, + 237, + 138, + 209, + 68, + 187, + 223, + 25, + 58, + 41, + 11, + 49, + 232, + 27, + 110, + 143, + 199, + 232, + 94, + 77, + 211, + 35, + 248, + 193, + 22, + 35, + 72, + 21, + 131, + 119, + 72, + 36, + 147, + 128, + 178, + 65, + 90, + 130, + 5, + 209, + 250, + 61, + 213, + 160, + 90, + 43, + 2, + 131, + 19, + 226, + 246, + 81, + 221, + 239, + 123, + 189, + 13 + ], + [ + 104, + 6, + 132, + 143, + 99, + 203, + 107, + 168, + 226, + 110, + 87, + 18, + 118, + 73, + 161, + 179, + 208, + 84, + 39, + 0, + 104, + 221, + 252, + 90, + 5, + 228, + 234, + 204, + 53, + 226, + 71, + 47, + 225, + 191, + 154, + 166, + 153, + 187, + 124, + 208, + 162, + 24, + 185, + 248, + 55, + 215, + 134, + 142, + 23, + 27, + 31, + 89, + 83, + 35, + 74, + 176, + 173, + 246, + 227, + 214, + 134, + 23, + 127, + 3 + ] + ] + } + }, + { + "size": 908, + "sectionType": "kContributions", + "contr": { + "hash": [ + 208, + 4, + 157, + 173, + 108, + 62, + 105, + 70, + 107, + 29, + 159, + 18, + 46, + 119, + 79, + 103, + 136, + 108, + 45, + 191, + 16, + 216, + 136, + 176, + 112, + 116, + 177, + 201, + 149, + 135, + 77, + 148, + 149, + 104, + 199, + 58, + 20, + 127, + 255, + 144, + 83, + 193, + 133, + 0, + 233, + 97, + 1, + 132, + 211, + 186, + 141, + 158, + 96, + 60, + 14, + 187, + 185, + 120, + 21, + 223, + 228, + 203, + 39, + 57 + ], + "num": 2 + } + } + ] +} \ No newline at end of file From 6ef7dd5e7cfc8e0dcd7c13acf62c48ca352c34db Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 20 Aug 2024 12:43:11 +0200 Subject: [PATCH 32/49] [nimble] add finite field FFT, Groth16 related tests to nimble file --- constantine.nimble | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/constantine.nimble b/constantine.nimble index 57d74c676..fa349893e 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -614,6 +614,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ # Polynomials # ---------------------------------------------------------- ("tests/math_polynomials/t_polynomials.nim", false), + ("tests/math_polynomials/t_finite_field_fft.nim", false), # Protocols # ---------------------------------------------------------- @@ -633,6 +634,9 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ # Proof systems # ---------------------------------------------------------- ("tests/proof_systems/t_r1cs_parser.nim", false), + ("tests/proof_systems/t_wtns_parser.nim", false), + ("tests/proof_systems/t_zkey_parser.nim", false), + ("tests/proof_systems/t_groth16_prover.nim", false), ("tests/interactive_proofs/t_multilinear_extensions.nim", false), ] From 928d4d1b01fe61aa621eec68dc63c452b892e95f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 22 Aug 2024 19:25:09 +0200 Subject: [PATCH 33/49] add booldefine variables to choose what is Montgomery encoded --- .../constraint_systems/wtns_binary_parser.nim | 10 +++++++-- .../constraint_systems/zkey_binary_parser.nim | 10 ++++++++- constantine/proof_systems/groth16_utils.nim | 21 ++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index a99e81451..75bce9014 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -99,10 +99,16 @@ func header*(wtns: WtnsBin): WitnessHeader = func witnesses*(wtns: WtnsBin): seq[Witness] = result = wtns.sections.filterIt(it.sectionType == kData)[0].wtns -proc getWitnesses[Name: static Algebra](witnesses: seq[Witness]): seq[Fr[Name]] = +proc getWitnesses*[Name: static Algebra](witnesses: seq[Witness]): seq[Fr[Name]] = result = newSeq[Fr[Name]](witnesses.len) for i, w in witnesses: - result[i] = toFr[Name](w.data, isMont = false) ## Important: Witness does *not* store numbers in Montgomery rep + when CM_WN or CMM_WN: + let isMont = false + elif CM_WM: + let isMont = true + else: + {.error: "One case must be active."} + result[i] = toFr[Name](w.data, isMont = isMont, isDoubleMont = false) ## Important: Witness does *not* store numbers in Montgomery rep proc toWtns*[Name: static Algebra](wtns: WtnsBin): Wtns[Name] = result = Wtns[Name]( diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index e16aa6861..1e2b57041 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -215,9 +215,17 @@ func to*[Name: static Algebra](coefs: Coefficients_b, _: typedesc[Coefficients[N m = coefs.cs[i].matrix c = coefs.cs[i].section s = coefs.cs[i].index + when CM_WN or CM_WM: + let isMont = true + let isDoubleMont = false + elif CMM_WN: + let isMont = false + let isDoubleMont = true + else: + {.error: "One case must be active.".} result.cs[i] = Coefficient[Name]( matrix: m, section: c, index: s, - value: toFr[Name](coefs.cs[i].value, true) + value: toFr[Name](coefs.cs[i].value, isMont = isMont, isDoubleMont = isDoubleMont) ) proc to*[Name: static Algebra](g16h: Groth16Header_b, _: typedesc[Groth16Header[Name]]): Groth16Header[Name] = diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index e4a2bbffb..6f8d9e483 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -5,6 +5,19 @@ import ../math/[arithmetic, extension_fields], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], ../csprngs/sysrand +## Read zkey coefficients `C` as montgomery rep `M`, witness data `W` in normal rep `N` +## This produces the correct zkey coefficients, but wrong `C` in `buildABC`. +## Proof g^A, g^B are correct. +const CM_WN* {.booldefine.} = false +## Read zkey coefficients `C` as montgomery rep `M`, witness data `W` in montgomery rep `M` +## This produces the correct............ +## No proof is correct. +const CM_WM* {.booldefine.} = false +## Read zkey coefficients `C` as double montgomery rep `MM`, witness data `W` in normal rep `N` +## This produces the correct 3 proof values +const CMM_WN* {.booldefine.} = false + +## What about `CN_WM`? ## Helper constructors for Fp / Fr elements used in Groth16 binary file parsers. proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = @@ -16,12 +29,18 @@ proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = else: result.fromBig(b) -proc toFr*[Name: static Algebra](x: seq[byte], isMont = true): Fr[Name] = +proc toFr*[Name: static Algebra](x: seq[byte], isMont = true, isDoubleMont = false): Fr[Name] = let b = matchingOrderBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian) if isMont: var bN: typeof(b) bN.fromMont(b, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) result.fromBig(bN) + elif isDoubleMont: + var bN: typeof(b) + bN.fromMont(b, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) + var bNN: typeof(b) + bNN.fromMont(bN, Fr[Name].getModulus(), Fr[Name].getNegInvModWord(), Fr[Name].getSpareBits()) + result.fromBig(bNN) else: result.fromBig(b) From e3cb336f3b29f8ccdec632b07a5de49eb2e4d640 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 22 Aug 2024 19:25:27 +0200 Subject: [PATCH 34/49] add a bunch more echo outputs for SnarkJS comparison --- constantine/proof_systems/manual_groth16.nim | 40 ++++++++++++++++---- tests/proof_systems/t_groth16_prover.nim | 14 +++++-- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/constantine/proof_systems/manual_groth16.nim b/constantine/proof_systems/manual_groth16.nim index e807aa799..027a51a99 100644 --- a/constantine/proof_systems/manual_groth16.nim +++ b/constantine/proof_systems/manual_groth16.nim @@ -52,21 +52,30 @@ proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): let alpha1 = g16h.alpha1 let delta1 = g16h.delta1 + echo "α1: ", alpha1.toHex() + echo "δ1: ", delta1.toHex() + # Declare `A_p` for the result var A_p: EC_ShortW_Jac[Fp[Name], G1] # Compute the terms independent of the witnesses - A_p = alpha1.getJacobian + ctx.r * delta1 - echo A_p.toHex() + #A_p = alpha1.getJacobian + ctx.r * delta1 + #echo A_p.toHex() let As = ctx.zkey.A doAssert As.len == wt.len for i in 0 ..< As.len: + echo "i = ", i, " As[i] = ", As[i].toHex() A_p += wt[i] * As[i] + echo "g^A MSM via loop: ", A_p.toHex() + A_p += alpha1.getJacobian + ctx.r * delta1 + + # Via MSM var A_p_msm: EC_ShortW_Jac[Fp[Name], G1] A_p_msm.multiScalarMul_vartime(wt, As) + echo "g^A MSM = ", A_p_msm.toHex() A_p_msm += alpha1.getJacobian + ctx.r * delta1 doAssert (A_p == A_p_msm).bool @@ -135,12 +144,17 @@ proc buildABC[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]) s = coeffs.cs[i].index coef = coeffs.cs[i].value assert s.int < wt.len + echo "Coef i = ", i, " = ", coef.toHex() outBuf[m][c] = outBuf[m][c] + coef * wt[s] # Compute C polynomial for i in 0 ..< domainSize: ## XXX: Here this product yields numbers in SnarkJS I cannot reproduce - outBuffC[i].prod(outBuffA[i], outBuffB[i]) + echo "OUTBUF A: ", outBuffA[i].toHex() + echo "OUTBUF B: ", outBuffB[i].toHex() + #outBuffC[i].prod(outBuffA[i], outBuffB[i]) + outBuffC[i] = outBuffA[i] * outBuffB[i] + echo "OUTBUF C: ", outBuffC[i].toHex() result = (outBuffA, outBuffB, outBuffC) @@ -189,11 +203,18 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW let B = itf(abc[1]) let C = itf(abc[2]) + for i in 0 ..< A.len: + echo "A after itf? ", A[i].toHex() + for i in 0 ..< B.len: + echo "B after itf? ", B[i].toHex() + for i in 0 ..< C.len: + echo "C after itf? ", C[i].toHex() + # combine A, B, C again var jabc = newSeq[Fr[Name]](A.len) for i in 0 ..< jabc.len: jabc[i] = A[i] * B[i] - C[i] - + echo "JABC? ", jabc[i].toHex() # Get the C data let Cs = ctx.zkey.C # Get private witnesses @@ -223,9 +244,9 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW C_p = ctx.s * A_p + ctx.r * B1_p - (ctx.r * ctx.s) * delta1 + cw + resH result = C_p -proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Jac[Fp[Name], G1], - B: EC_ShortW_Jac[Fp2[Name], G2], - C: EC_ShortW_Jac[Fp[Name], G1]] = +proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Aff[Fp[Name], G1], + B: EC_ShortW_Aff[Fp2[Name], G2], + C: EC_ShortW_Aff[Fp[Name], G1]] = #[ XXX: fix up notation here! r = random_scalar_field_element() @@ -246,13 +267,16 @@ proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_ ]# let wt = ctx.wtns.witnesses + echo "WITNESS DATA:" + for i, el in wt: + echo i, " = ", el.toHex() let A_p = ctx.calcAp(wt) let B2_p = ctx.calcBp(wt) let B1_p = ctx.calcB1(wt) let C_p = ctx.calcCp(A_p, B1_p, wt) - result = (A: A_p, B: B2_p, C: C_p) + result = (A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) when isMainModule: diff --git a/tests/proof_systems/t_groth16_prover.nim b/tests/proof_systems/t_groth16_prover.nim index d3e0041fc..3e41addf1 100644 --- a/tests/proof_systems/t_groth16_prover.nim +++ b/tests/proof_systems/t_groth16_prover.nim @@ -38,6 +38,9 @@ suite "Groth16 prover": ctx.r = r ctx.s = s + echo "r = ", ctx.r.toHex() + echo "s = ", ctx.s.toHex() + # expected values produced by SnarkJS with these `r`, `s` values # x/y coordinates of Fp point on G1 subgroup of EC, corresponding to `g^A_1` const ax = "5525629793372463776337933283524928112323589665400780041477380790923758613749" @@ -90,7 +93,12 @@ suite "Groth16 prover": echo "C_p#16 = ", C_p.toHex() echo "C_p#10 = ", C_p.toDecimal() - check (A_p == aExp.getJacobian).bool - check (B2_p == bExp.getJacobian).bool + #check (A_p == aExp.getJacobian).bool + #check (B2_p == bExp.getJacobian).bool + ### XXX: C currently fails! + #check (C_p == cExp.getJacobian).bool + + check (A_p == aExp).bool + check (B2_p == bExp).bool ## XXX: C currently fails! - check (C_p == cExp.getJacobian).bool + check (C_p == cExp).bool From c92ff5a8e8f4c4403d282e7e3b942dccc2681fe1 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 10:45:40 +0100 Subject: [PATCH 35/49] [parser] minor cleanup of the binary file parsing logic --- .../constraint_systems/r1cs_circom_parser.nim | 24 ++----- .../constraint_systems/wtns_binary_parser.nim | 50 ++++---------- .../constraint_systems/zkey_binary_parser.nim | 68 ++++++------------- 3 files changed, 40 insertions(+), 102 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim index 80f7aa7d8..531bed357 100644 --- a/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim +++ b/constantine/proof_systems/constraint_systems/r1cs_circom_parser.nim @@ -18,7 +18,8 @@ import ../../serialization/[io_limbs, parsing], - constantine/platforms/[fileio, abstractions] + constantine/platforms/[fileio, abstractions], + ./parser_utils # We use `sortedByIt` to sort the different sections in the file by their # `R1csSectionKind` @@ -173,27 +174,12 @@ proc parseConstraint(f: File, constraint: var Constraint, fieldSize: int32): boo ?f.parseLinComb(constraint.C, fieldSize) return true -template r1csSection(sectionSize, body: untyped): untyped = - let startOffset = f.getFilePosition() - - body - - return sectionSize.int == f.getFilePosition() - startOffset - proc parseConstraints(f: File, constraints: var seq[Constraint], sectionSize: uint64, numConstraints, fieldSize: int32): bool = - r1csSection(sectionSize): + parseCheck(sectionSize): # returns boolean check constraints.setLen(numConstraints) for constraint in constraints.mitems(): ?f.parseConstraint(constraint, fieldSize) -proc parseMagicHeader(f: File, mh: var array[4, char]): bool = - result = f.readInto(mh) - -proc parseSectionKind(f: File, v: var R1csSectionKind): bool = - var val: uint32 - result = f.parseInt(val, littleEndian) - v = R1csSectionKind(val.int) - proc parseHeader(f: File, h: var Header): bool = ?f.parseInt(h.fieldSize, littleEndian) # byte size of the prime number # allocate for the prime @@ -208,7 +194,7 @@ proc parseHeader(f: File, h: var Header): bool = result = true # would have returned before due to `?` otherwise proc parseWire2Label(f: File, v: var Wire2Label, sectionSize: uint64): bool = - r1csSection(sectionSize): + parseCheck(sectionSize): # returns boolean check let numWires = sectionSize div 8 v.wireIds.setLen(numWires) for labelId in v.wireIds.mitems(): @@ -258,7 +244,7 @@ proc parseR1csFile*(path: string): R1csBin = for i in 0 ..< result.numberSections: var kind: R1csSectionKind var size: uint64 # section size - doAssert f.parseSectionKind(kind), "Failed to read section type in section " & $i + doAssert f.parseSectionKind[:R1csSectionKind](kind), "Failed to read section type in section " & $i doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " & $i # compute position of next section pos[i] = (kind, size, f.getFilePosition()) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index 75bce9014..2ef39f716 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -12,7 +12,8 @@ import ../../serialization/[io_limbs, parsing], constantine/platforms/[fileio, abstractions], ../../named/algebras, # Fr - ../groth16_utils + ../groth16_utils, + ./parser_utils #[ The following is a rough spec of the witness files. Details may vary for different @@ -120,21 +121,6 @@ proc toWtns*[Name: static Algebra](wtns: WtnsBin): Wtns[Name] = proc initSection(kind: WtnsSectionKind, size: uint64): Section = result = Section(sectionType: kind, size: size) -template wtnsSection(sectionSize, body: untyped): untyped = - let startOffset = f.getFilePosition() - - body - - return sectionSize.int == f.getFilePosition() - startOffset - -proc parseMagicHeader(f: File, mh: var array[4, char]): bool = - result = f.readInto(mh) - -proc parseSectionKind(f: File, v: var WtnsSectionKind): bool = - var val: uint32 - result = f.parseInt(val, littleEndian) - v = WtnsSectionKind(val.int) - proc parseWitnessHeader(f: File, h: var WitnessHeader): bool = ?f.parseInt(h.n8, littleEndian) # byte size of the prime number h.r.setLen(h.n8) @@ -144,19 +130,19 @@ proc parseWitnessHeader(f: File, h: var WitnessHeader): bool = proc parseWitnesses(f: File, s: var seq[Witness], sectionSize: uint64, elemSize: uint32): bool = ## Parses the witnesses - let numElems = sectionSize div elemSize.uint64 - var buf = newSeq[byte](elemSize) - s.setLen(numElems) - for i in 0 ..< numElems: - ?f.readInto(buf) - s[i] = Witness(data: buf) ## XXX: fix me - result = true + parseCheck(sectionSize): # returns boolean check + let numElems = sectionSize div elemSize.uint64 + var buf = Witness(data: newSeq[byte](elemSize)) + s.setLen(numElems) + for i in 0 ..< numElems: + ?f.readInto(buf.data) + s[i] = buf proc parseWitnesses(f: File, s: var Section, size: uint64, wtns: WtnsBin): bool = let h = wtns.sections.filterIt(it.sectionType == kHeader)[0].header ## XXX: fixme result = parseWitnesses(f, s.wtns, size, h.n8) -proc parseSection(f: File, s: var Section, kind: WtnsSectionKind, size: uint64, wtns: var WtnsBin): bool = +proc parseSection(f: File, s: var Section, kind: WtnsSectionKind, size: uint64, wtns: WtnsBin): bool = # NOTE: The `wtns` object is there to provide the header information to # the constraints section s = initSection(kind, size) @@ -167,16 +153,6 @@ proc parseSection(f: File, s: var Section, kind: WtnsSectionKind, size: uint64, result = true # would have returned otherwise due to `?` -proc parseSection(f: File, wtns: var WtnsBin): Section = - var kind: WtnsSectionKind - var size: uint64 - doAssert f.parseSectionKind(kind), "Failed to read section type in section " - doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " - - result = initSection(kHeader, size) - - doAssert f.parseSection(result, kind, size, wtns), "Failed to parse section: " & $kind - proc parseWtnsFile*(path: string): WtnsBin = var f = fileio.open(path, kRead) @@ -184,8 +160,8 @@ proc parseWtnsFile*(path: string): WtnsBin = doAssert f.parseInt(result.version, littleEndian), "Failed to read version" doAssert f.parseInt(result.numberSections, littleEndian), "Failed to read number of sections" - for i in 0 ..< result.numberSections: - let s = parseSection(f, result) - result.sections.add s + result.sections = newSeq[Section](result.numberSections) + for sec in mitems(result.sections): + doAssert f.parseSection(sec, result, WtnsSectionKind), "Failed to parse section: " & $sec.sectionType fileio.close(f) diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index 1e2b57041..89af35290 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -14,7 +14,8 @@ import ../../named/algebras, # Fr, Fp ../../math/extension_fields, # Fp2 ../../math/elliptic/[ec_shortweierstrass_affine], # EC types - ../groth16_utils # to unmarshal data + ../groth16_utils, # to unmarshal data + ./parser_utils from std / sequtils import filterIt from std / strutils import endsWith @@ -270,21 +271,6 @@ proc toZkey*[Name: static Algebra](zkey: ZkeyBin): Zkey[Name] = proc initSection(kind: ZkeySectionKind, size: uint64): Section = result = Section(sectionType: kind, size: size) -template zkeySection(sectionSize, body: untyped): untyped = - let startOffset = f.getFilePosition() - - body - - return sectionSize.int == f.getFilePosition() - startOffset - -proc parseMagicHeader(f: File, mh: var array[4, char]): bool = - result = f.readInto(mh) - -proc parseSectionKind(f: File, v: var ZkeySectionKind): bool = - var val: uint32 - result = f.parseInt(val, littleEndian) - v = ZkeySectionKind(val.int) - proc parseHeader(f: File, h: var Header): bool = ?f.parseInt(h.proverType, littleEndian) # byte size of the prime number doAssert h.proverType == 1, "Prover type must be `1` for Groth16, found: " & $h.proverType @@ -318,15 +304,15 @@ proc parseGroth16Header(f: File, g16h: var Groth16Header_b): bool = proc parseDataSection(f: File, d: var DataSection, sectionSize: uint64, elemSize: uint32): bool = ## Parses a generic data section, each `elemSize` in size - let numElems = sectionSize div elemSize.uint64 - var buf = newSeq[byte](elemSize) - d.points.setLen(numElems.int) - for i in 0 ..< numElems: - ?f.readInto(buf) - d.points[i] = buf ## XXX: fix me - result = true + parseCheck(sectionSize): # returns boolean check + let numElems = sectionSize div elemSize.uint64 + var buf = newSeq[byte](elemSize) + d.points.setLen(numElems.int) + for i in 0 ..< numElems: + ?f.readInto(buf) + d.points[i] = buf -proc parseDatasection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: var ZkeyBin): bool = +proc parseDatasection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: ZkeyBin): bool = let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme case kind of kIC: ?f.parseDataSection(s.ic, size, 2 * g16h.n8q) @@ -347,13 +333,13 @@ proc parseCoefficient(f: File, s: var Coefficient_b, size: uint64): bool = ?f.readInto(s.value) result = true -proc parseCoefficients(f: File, s: var Coefficients_b, zkey: ZkeyBin): bool = - ?f.parseInt(s.num, littleEndian) - let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme - s.cs = newSeq[Coefficient_b](s.num) - for i in 0 ..< s.num: # parse coefficients - ?f.parseCoefficient(s.cs[i], g16h.n8r) - result = true +proc parseCoefficients(f: File, s: var Coefficients_b, sectionSize: uint64, zkey: ZkeyBin): bool = + parseCheck(sectionSize): # returns boolean check + ?f.parseInt(s.num, littleEndian) + let g16h = zkey.sections.filterIt(it.sectionType == kGroth16Header)[0].g16h ## XXX: fixme + s.cs = newSeq[Coefficient_b](s.num) + for i in 0 ..< s.num: # parse coefficients + ?f.parseCoefficient(s.cs[i], g16h.n8r) proc parseContributions(f: File, s: var Contributions): bool = ?f.readInto(s.hash) @@ -361,7 +347,7 @@ proc parseContributions(f: File, s: var Contributions): bool = # XXX: parse individual contributions result = true -proc parseSection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: var ZkeyBin): bool = +proc parseSection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, zkey: ZkeyBin): bool = # NOTE: The `zkey` object is there to provide the header information to # the constraints section s = initSection(kind, size) @@ -369,22 +355,12 @@ proc parseSection(f: File, s: var Section, kind: ZkeySectionKind, size: uint64, of kHeader: ?f.parseHeader(s.header) of kGroth16Header: ?f.parseGroth16Header(s.g16h) of kIC, kA .. kH: ?f.parseDataSection(s, kind, size, zkey) - of kCoeffs: ?f.parseCoefficients(s.coeffs, zkey) + of kCoeffs: ?f.parseCoefficients(s.coeffs, size, zkey) of kContributions: ?f.parseContributions(s.contr) else: raiseAssert "Invalid" result = true # would have returned otherwise due to `?` -proc parseSection(f: File, zkey: var ZkeyBin): Section = - var kind: ZkeySectionKind - var size: uint64 - doAssert f.parseSectionKind(kind), "Failed to read section type in section " - doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " - - result = initSection(kHeader, size) - - doAssert f.parseSection(result, kind, size, zkey), "Failed to parse section: " & $kind - proc parseZkeyFile*(path: string): ZkeyBin = var f = fileio.open(path, kRead) @@ -392,8 +368,8 @@ proc parseZkeyFile*(path: string): ZkeyBin = doAssert f.parseInt(result.version, littleEndian), "Failed to read version" doAssert f.parseInt(result.numberSections, littleEndian), "Failed to read number of sections" - for i in 0 ..< result.numberSections: - let s = parseSection(f, result) - result.sections.add s + result.sections = newSeq[Section](result.numberSections) + for sec in mitems(result.sections): + doAssert f.parseSection(sec, result, ZkeySectionKind), "Failed to parse section: " & $sec.sectionType fileio.close(f) From 5ceda053172e009b0c173e2bdb5c847daa21a995 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 10:45:50 +0100 Subject: [PATCH 36/49] remove comment about truncation in random field element sampling --- constantine/proof_systems/groth16_utils.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index 6f8d9e483..edfbe3fa5 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -82,6 +82,7 @@ proc randomFieldElement*[Name: static Algebra](_: typedesc[Fr[Name]]): Fr[Name] var b: matchingOrderBigInt(Name) bind sysrand - while b.isZero().bool or (b > m).bool: ## XXX: or just truncate? + # sample until we find an element less than the modulo (truncating would give biased sampler) + while b.isZero().bool or (b > m).bool: assert b.limbs.sysrand() result.fromBig(b) From 694755fb80a17908061fc15a2b7e697a391d862d Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 10:46:03 +0100 Subject: [PATCH 37/49] remove debugging ~booldefine~ variables --- .../constraint_systems/wtns_binary_parser.nim | 9 ++------- .../constraint_systems/zkey_binary_parser.nim | 12 +++--------- constantine/proof_systems/groth16_utils.nim | 8 -------- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim index 2ef39f716..9ef085b0a 100644 --- a/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/wtns_binary_parser.nim @@ -103,13 +103,8 @@ func witnesses*(wtns: WtnsBin): seq[Witness] = proc getWitnesses*[Name: static Algebra](witnesses: seq[Witness]): seq[Fr[Name]] = result = newSeq[Fr[Name]](witnesses.len) for i, w in witnesses: - when CM_WN or CMM_WN: - let isMont = false - elif CM_WM: - let isMont = true - else: - {.error: "One case must be active."} - result[i] = toFr[Name](w.data, isMont = isMont, isDoubleMont = false) ## Important: Witness does *not* store numbers in Montgomery rep + ## Important: Witness does *not* store numbers in Montgomery rep + result[i] = toFr[Name](w.data, isMont = false, isDoubleMont = false) proc toWtns*[Name: static Algebra](wtns: WtnsBin): Wtns[Name] = result = Wtns[Name]( diff --git a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim index 89af35290..9a970efd3 100644 --- a/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim +++ b/constantine/proof_systems/constraint_systems/zkey_binary_parser.nim @@ -216,17 +216,11 @@ func to*[Name: static Algebra](coefs: Coefficients_b, _: typedesc[Coefficients[N m = coefs.cs[i].matrix c = coefs.cs[i].section s = coefs.cs[i].index - when CM_WN or CM_WM: - let isMont = true - let isDoubleMont = false - elif CMM_WN: - let isMont = false - let isDoubleMont = true - else: - {.error: "One case must be active.".} + ## NOTE: The coefficients are *doubly* Montgomery encoded in SnarkJS' ZKey binary files! + ## See `groth16_utils.toFr` for more details. result.cs[i] = Coefficient[Name]( matrix: m, section: c, index: s, - value: toFr[Name](coefs.cs[i].value, isMont = isMont, isDoubleMont = isDoubleMont) + value: toFr[Name](coefs.cs[i].value, isMont = false, isDoubleMont = true) ) proc to*[Name: static Algebra](g16h: Groth16Header_b, _: typedesc[Groth16Header[Name]]): Groth16Header[Name] = diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index edfbe3fa5..693830649 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -5,14 +5,6 @@ import ../math/[arithmetic, extension_fields], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], ../csprngs/sysrand -## Read zkey coefficients `C` as montgomery rep `M`, witness data `W` in normal rep `N` -## This produces the correct zkey coefficients, but wrong `C` in `buildABC`. -## Proof g^A, g^B are correct. -const CM_WN* {.booldefine.} = false -## Read zkey coefficients `C` as montgomery rep `M`, witness data `W` in montgomery rep `M` -## This produces the correct............ -## No proof is correct. -const CM_WM* {.booldefine.} = false ## Read zkey coefficients `C` as double montgomery rep `MM`, witness data `W` in normal rep `N` ## This produces the correct 3 proof values const CMM_WN* {.booldefine.} = false From 65b4d532b021d58a003ddb127012eec3374bc590 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 13:32:44 +0100 Subject: [PATCH 38/49] [groth16] add parser utils submodule for Groth16 binary files --- .../constraint_systems/parser_utils.nim | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 constantine/proof_systems/constraint_systems/parser_utils.nim diff --git a/constantine/proof_systems/constraint_systems/parser_utils.nim b/constantine/proof_systems/constraint_systems/parser_utils.nim new file mode 100644 index 000000000..e1fd12a4c --- /dev/null +++ b/constantine/proof_systems/constraint_systems/parser_utils.nim @@ -0,0 +1,35 @@ +import constantine/platforms/[fileio, abstractions] + +proc parseMagicHeader*(f: File, mh: var array[4, char]): bool = + ## Parses the magic header of the 3 types of binary files into `mh` + result = f.readInto(mh) + +proc parseSectionKind*[T](f: File, v: var T): bool = + ## Parses a section kind `T` which should be one of + ## `ZkeySectionKind`, `WtnsSectionKind` or `R1csSectionKind` + var val: uint32 + result = f.parseInt(val, littleEndian) + v = T(val.int) + +template parseCheck*(sectionSize, body: untyped): untyped = + let startOffset = f.getFilePosition() + + body + + return sectionSize.int == f.getFilePosition() - startOffset + +proc parseSection*[S; T; U](f: File, sec: var S, bin: T, sectionKind: typedesc[U]): bool = + ## Parses the section into `sec`. + ## Note: not used for R1CS, because we already preparse the section headers to + ## be able to parse the sections in the correct order. + var kind: sectionKind # Zkey/Wtns SectionKind + var size: uint64 + doAssert f.parseSectionKind[:sectionKind](kind), "Failed to read section type in section " + doAssert f.parseInt(size, littleEndian), "Failed to read section size in section " + + mixin initSection + mixin kHeader + sec = initSection(kHeader, size) + + mixin parseSection + result = f.parseSection(sec, kind, size, bin) From 854dc5e5f4868ffdee9cc61e5c2223347df4de9a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 13:33:04 +0100 Subject: [PATCH 39/49] [groth16] rename the Groth16 main file --- constantine/proof_systems/{manual_groth16.nim => groth16.nim} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename constantine/proof_systems/{manual_groth16.nim => groth16.nim} (100%) diff --git a/constantine/proof_systems/manual_groth16.nim b/constantine/proof_systems/groth16.nim similarity index 100% rename from constantine/proof_systems/manual_groth16.nim rename to constantine/proof_systems/groth16.nim From 9a33863eacef207173150469120d6076919f8dd7 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 13:43:27 +0100 Subject: [PATCH 40/49] clean up FFT LUT calculation code --- constantine/math/polynomials/fft_lut.nim | 31 +----------------------- 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/constantine/math/polynomials/fft_lut.nim b/constantine/math/polynomials/fft_lut.nim index d1187a7bb..542349c3e 100644 --- a/constantine/math/polynomials/fft_lut.nim +++ b/constantine/math/polynomials/fft_lut.nim @@ -62,7 +62,6 @@ proc decomposeFieldOrder(F: type Fr): tuple[s: F, e: uint64] = # `s = p - 1` var s {.noInit.}: BigInt[F.bits()] s = F.getModulus() - debugecho "Exp: ", s.tohex() s -= One # `e = 0` @@ -70,7 +69,6 @@ proc decomposeFieldOrder(F: type Fr): tuple[s: F, e: uint64] = while s.isEven().bool: s.shiftRight(1) - #e += F.fromUint(1'u64) inc e result = (s: F.fromBig(s), e: e) @@ -90,47 +88,20 @@ proc findAnyQnr(F: type Fr): F = func buildRootLUT(F: type Fr, primitive_root: uint64): array[32, F] = ## [pow(PRIMITIVE_ROOT, (MODULUS - 1) // (2**i), MODULUS) for i in range(32)] - ## - ## XXX: For some reason this does not work for BN254_Snarks (haven't tried with BLS12-381) - let (s, e) = decomposeFieldOrder(F) let qnr = findAnyQnr(F) - #var exponent {.noInit.}: BigInt[F.bits()] - #exponent = F.getModulus() - #debugecho "Exp: ", exponent.tohex() - #exponent -= One - # - ## Start by the end - #var i = result.len - 1 - #exponent.shiftRight(i) - #debugecho "last Exponent : ", exponent.toHex() - #result[i].fromUint(primitive_root) - #result[i].pow_vartime(exponent) - - - var i = e.int #e.toDecimal() # largest possible power of 2 for this field + var i = e.int var rootUnity = qnr rootUnity.pow_vartime(s) result[i] = rootUnity - while i > 0: result[i-1].square(result[i]) - #debugecho "At ", i, ": ", result[i-1].toHex() - dec i - # debugEcho "Fr[BLS12_81] - Roots of Unity:" - # for i in 0 ..< result.len: - # debugEcho " ", i, ": ", result[i].toHex() - # debugEcho "Fr[BLS12_81] - Roots of Unity -- FIN\n" - #let BLS12_381_Fr_ScaleToRootOfUnity* = buildRootLUT(Fr[BLS12_381], BLS12_381_Fr_primitive_root) let BN254_Snarks_Fr_ScaleToRootOfUnity* = buildRootLUT(Fr[BN254_Snarks], BN254_Snarks_Fr_primitive_root) -let g2 = BN254_Snarks_Fr_ScaleToRootOfUnity # [28 - order] -for i, el in g2: - debugecho "ω^{2^", i, "} = ", el.toHex() {.experimental: "dynamicBindSym".} macro scaleToRootOfUnity*(Name: static Algebra): untyped = From 28cb7e27824dc3f1d9fad666efa9e71da5f8ff65 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 13:43:58 +0100 Subject: [PATCH 41/49] [groth16] clean up Groth16 main file --- constantine/proof_systems/groth16.nim | 277 +++++++------------------- 1 file changed, 73 insertions(+), 204 deletions(-) diff --git a/constantine/proof_systems/groth16.nim b/constantine/proof_systems/groth16.nim index 027a51a99..d607b15fc 100644 --- a/constantine/proof_systems/groth16.nim +++ b/constantine/proof_systems/groth16.nim @@ -29,83 +29,60 @@ type wtns*: Wtns[Name] r1cs*: R1CS # secret random values `r`, `s` for the proof - ## XXX: These won't remain public of course - r*: Fr[Name] - s*: Fr[Name] + r: Fr[Name] + s: Fr[Name] proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: Zkey[Name], wtns: Wtns[Name], r1cs: R1CS): Groth16Prover[Name] = result = Groth16Prover[Name]( zkey: zkey, wtns: wtns, - r1cs: r1cs, - r: randomFieldElement(Fr[Name]), ## XXX: do we want to do this in `init`? - s: randomFieldElement(Fr[Name]) + r1cs: r1cs ) -proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = - # A_p is defined as - # A_p = α_1 + (Σ_i [W]_i · A_i) + [r] · δ_1 - # A_p = alpha1 + sum(A[i] * witness[i] for i in range(zkey.g16h.nVars)) + r * delta1 - # where of course in principle `α_1` is `g_1^{α}` etc. +proc calcAp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] {.noinit.} = + # A_p is defined as: + # `A_p = α₁ + (Σ_i [W]_i · A_i) + [r] · δ₁` + # or closer to code: + # `A_p = α₁ + sum([witness[i]] · A[i] for i in range(zkey.g16h.nVars)) + [r] · δ₁` + # where of course in principle `α₁` is `g₁^{α}` etc. let g16h = ctx.zkey.g16h let alpha1 = g16h.alpha1 let delta1 = g16h.delta1 - echo "α1: ", alpha1.toHex() - echo "δ1: ", delta1.toHex() - - # Declare `A_p` for the result - var A_p: EC_ShortW_Jac[Fp[Name], G1] - # Compute the terms independent of the witnesses - #A_p = alpha1.getJacobian + ctx.r * delta1 - #echo A_p.toHex() - let As = ctx.zkey.A doAssert As.len == wt.len - for i in 0 ..< As.len: - echo "i = ", i, " As[i] = ", As[i].toHex() - A_p += wt[i] * As[i] - - echo "g^A MSM via loop: ", A_p.toHex() + var A_p {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + # Calculate `Σ_i [W]_i · A_i` via MSM + A_p.multiScalarMul_vartime(wt, As) + # Add the independent terms, `α₁ + [r] · δ₁` A_p += alpha1.getJacobian + ctx.r * delta1 - - # Via MSM - var A_p_msm: EC_ShortW_Jac[Fp[Name], G1] - A_p_msm.multiScalarMul_vartime(wt, As) - echo "g^A MSM = ", A_p_msm.toHex() - A_p_msm += alpha1.getJacobian + ctx.r * delta1 - - doAssert (A_p == A_p_msm).bool - result = A_p -proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp2[Name], G2] = - # B_p = beta2 + sum(B2[i] * witness[i] for i in range(zkey.g16h.nVars)) + s * delta2 - # B_p = β_2 + (Σ_i [W]_i · B2_i) + [s] · δ_2 - # where of course in principle `β_1` is `g_1^{β}` etc. +proc calcBp[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp2[Name], G2] {.noinit.} = + # B_p is defined as: + # `B_p = β₂ + (Σ_i [W]_i · B2_i) + [s] · δ₂` + # or closer to code: + # `B_p = β₂ + sum([witness[i]] · B2[i] for i in range(zkey.g16h.nVars)) + [s] · δ₂` + # where of course in principle `β₁` is `g₁^{β}` etc. let g16h = ctx.zkey.g16h let beta2 = g16h.beta2 let delta2 = g16h.delta2 - # Declare `B_p` for the result - var B_p: EC_ShortW_Jac[Fp2[Name], G2] - - # Compute the terms independent of the witnesses - B_p = beta2.getJacobian + ctx.s * delta2 - let Bs = ctx.zkey.B2 doAssert Bs.len == wt.len - # could compute via MSM - for i in 0 ..< Bs.len: - B_p += wt[i] * Bs[i] + # Calculate `Σ_i [W]_i · B2_i` via MSM + var B_p {.noinit.}: EC_ShortW_Jac[Fp2[Name], G2] + B_p.multiScalarMul_vartime(wt, Bs) + # Add the terms independent of the witnesses, `β₂ + [s] · δ₂` + B_p += beta2.getJacobian + ctx.s * delta2 result = B_p -proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = +proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] {.noinit.} = let g16h = ctx.zkey.g16h let beta1 = g16h.beta1 @@ -115,8 +92,13 @@ proc calcB1[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): # Get the B1 data let Bs = ctx.zkey.B1 doAssert Bs.len == wt.len - for i in 0 ..< Bs.len: - result += wt[i] * Bs[i] + var B1_p {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + B1_p.multiScalarMul_vartime(wt, Bs) + # Add the independent terms, `β₁ + [s] · δ₁` + B1_p += beta1.getJacobian + ctx.s * delta1 + + result = B1_p + proc buildABC[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]): tuple[A, B, C: seq[Fr[Name]]] = # Extract required data using accessors @@ -144,17 +126,13 @@ proc buildABC[Name: static Algebra](ctx: Groth16Prover[Name], wt: seq[Fr[Name]]) s = coeffs.cs[i].index coef = coeffs.cs[i].value assert s.int < wt.len - echo "Coef i = ", i, " = ", coef.toHex() - outBuf[m][c] = outBuf[m][c] + coef * wt[s] + var cf {.noinit.}: Fr[Name] + cf.prod(coef, wt[s]) + outBuf[m][c].sum(outBuf[m][c], cf) # Compute C polynomial for i in 0 ..< domainSize: - ## XXX: Here this product yields numbers in SnarkJS I cannot reproduce - echo "OUTBUF A: ", outBuffA[i].toHex() - echo "OUTBUF B: ", outBuffB[i].toHex() - #outBuffC[i].prod(outBuffA[i], outBuffB[i]) - outBuffC[i] = outBuffA[i] * outBuffB[i] - echo "OUTBUF C: ", outBuffC[i].toHex() + outBuffC[i].prod(outBuffA[i], outBuffB[i]) result = (outBuffA, outBuffB, outBuffC) @@ -169,7 +147,7 @@ proc transform[Name: static Algebra](args: seq[Fr[Name]], inc: Fr[Name]): seq[Fr result = newSeq[Fr[Name]](args.len) var cur = Fr[Name].fromUint(1.uint64) for i in 0 ..< args.len: - result[i] = args[i] * cur + result[i].prod(args[i], cur) cur *= inc proc itf[Name: static Algebra](arg: seq[Fr[Name]]): seq[Fr[Name]] = @@ -189,11 +167,13 @@ proc itf[Name: static Algebra](arg: seq[Fr[Name]]): seq[Fr[Name]] = result = fft_vartime(buffAodd) -proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW_Jac[Fp[Name], G1], wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] = - # # Compute C_p - # C_p = sum(C[i] * witness[i] for i in range(zkey.g16h.nVars)) - # C_p += A_p * s + r * B_p - r * s * delta1 - # C_p += sum(H[i] * (witness[i] * witness[j]) for i, j in r1cs.constraints) +proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], + A_p, B1_p: EC_ShortW_Jac[Fp[Name], G1], + wt: seq[Fr[Name]]): EC_ShortW_Jac[Fp[Name], G1] {.noinit.} = + # C_p is defined as: + # `C_p = sum([witness[i]] · C[i] for i in range(zkey.g16h.nVars))` + # `C_p += [s] · A_p + [r] · B_p - [r] · [s] · δ₁` + # `C_p += sum(([witness[i]] · [witness[j]]) · H[i] for i, j in r1cs.constraints)` let abc = buildABC[Name](ctx, wt) @@ -203,172 +183,61 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], A_p, B1_p: EC_ShortW let B = itf(abc[1]) let C = itf(abc[2]) - for i in 0 ..< A.len: - echo "A after itf? ", A[i].toHex() - for i in 0 ..< B.len: - echo "B after itf? ", B[i].toHex() - for i in 0 ..< C.len: - echo "C after itf? ", C[i].toHex() - # combine A, B, C again var jabc = newSeq[Fr[Name]](A.len) for i in 0 ..< jabc.len: - jabc[i] = A[i] * B[i] - C[i] - echo "JABC? ", jabc[i].toHex() + jabc[i].prod(A[i], B[i]) # `A_i · B_i` + jabc[i].diff(jabc[i], C[i]) # `A_i · B_i - C_i` + # Get the C data let Cs = ctx.zkey.C # Get private witnesses let g16h = ctx.zkey.g16h - ## XXX: Why is `nPublic` `1` when `Cs.len` ends up as `4` and `nVars` is `6`? - echo "LEN ? ", Cs.len, " total witnesses? ", wt.len, " public? ", g16h.nPublic, " total? ", g16h.nVars - var priv = newSeqOfCap[Fr[Name]](wt.len) - let nPub = g16h.nVars.int - Cs.len # g16h.nPublic.int + # get all private witnesses. First nPub are public + + ## NOTE: `g16h.nPublic` does not match number of public variables for some reason, + ## hence we compute it from `(# total witnesses - # coefficients)` + let nPub = g16h.nVars.int - Cs.len + var priv = newSeq[Fr[Name]](Cs.len) for i in nPub ..< g16h.nVars.int: - priv.add wt[i] + priv[i - nPub] = wt[i] + # Calculate `[witness[i]] · C[i]` using MSM doAssert Cs.len == priv.len, " Cs: " & $Cs.len & ", priv: " & $priv.len - var cw: EC_ShortW_Jac[Fp[Name], G1] - for i in 0 ..< Cs.len: - cw += priv[i] * Cs[i] + var cw {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + cw.multiScalarMul_vartime(priv, Cs) + # Calculate `[witness[i]] · [witness[j]] · H[i]` using MSM let Hs = ctx.zkey.H doAssert Hs.len == jabc.len - var resH: EC_ShortW_Jac[Fp[Name], G1] - for i in 0 ..< Hs.len: - resH += jabc[i] * Hs[i] + var resH {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + resH.multiScalarMul_vartime(jabc, Hs) let delta1 = g16h.delta1 # Declare `C_p` for the result - var C_p: EC_ShortW_Jac[Fp[Name], G1] + var C_p {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + # Combine all terms into final result C_p = ctx.s * A_p + ctx.r * B1_p - (ctx.r * ctx.s) * delta1 + cw + resH result = C_p proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Aff[Fp[Name], G1], B: EC_ShortW_Aff[Fp2[Name], G2], - C: EC_ShortW_Aff[Fp[Name], G1]] = - #[ - XXX: fix up notation here! - r = random_scalar_field_element() - s = random_scalar_field_element() - - # Compute A_p - A_p = alpha1 + sum(A[i] * witness[i] for i in range(zkey.g16h.nVars)) + r * delta1 - - # Compute B_p - B_p = beta2 + sum(B2[i] * witness[i] for i in range(zkey.g16h.nVars)) + s * delta2 - - # Compute C_p - C_p = sum(C[i] * witness[i] for i in range(zkey.g16h.nVars)) - C_p += A_p * s + r * B_p - r * s * delta1 - C_p += sum(H[i] * (witness[i] * witness[j]) for i, j in r1cs.constraints) - - proof = (A_p, B_p, C_p) - ]# - + C: EC_ShortW_Aff[Fp[Name], G1]] {.noinit.} = + ## Generate a proof given the Groth16 prover context data. + ## + ## This implies calculating the proof elements `π = (g₁^A, g₁^C, g₂^B)` + ## + ## See `calcAp`, `calcBp` and `calcCp` on how these elements are computed. + # 1. Sample the random field elements `r` and `s` for the proof + ctx.r = randomFieldElement(Fr[Name]) + ctx.s = randomFieldElement(Fr[Name]) + # 2. get the witness data needed for all proof elements let wt = ctx.wtns.witnesses - echo "WITNESS DATA:" - for i, el in wt: - echo i, " = ", el.toHex() - + # 3. compute the individual proof elements let A_p = ctx.calcAp(wt) let B2_p = ctx.calcBp(wt) let B1_p = ctx.calcB1(wt) let C_p = ctx.calcCp(A_p, B1_p, wt) result = (A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) - -when isMainModule: - - const T = BN254_Snarks - - let wtns = parseWtnsFile("/home/basti/org/constantine/moonmath/circom/three_fac_js/witness.wtns") - .toWtns[:T]() - let zkey = parseZkeyFile("/home/basti/org/constantine/moonmath/snarkjs/three_fac/three_fac_final.zkey") - .toZkey[:T]() - let r1cs = parseR1csFile("/home/basti/org/constantine/moonmath/circom/three_fac.r1cs") - .toR1CS - - - let g16h = zkey.g16h - ## NOTE: We *expect* all these to be 0, because they are the respective moduli for - ## `Fp` and `Fr`! - ## XXX: move to a test case - echo "q = ", toFp[T](g16h.q, false).toDecimal() - echo "r = ", toFr[T](g16h.r, false).toDecimal() - echo "wtns r = ", toFr[T](wtns.header.r, false).toDecimal() - - var ctx = Groth16Prover[T].init(zkey, wtns, r1cs) - - ## Note: We will now calculate the proof using a fixed, non secret set of points - ## r, s (or r, t) ∈ 𝔽r in order to compare with a calculation of `snarkjs`. We - ## hacked in a print of the secret it randomly sampled. - # The 'secret' constants from - ## XXX: Move to a test case! - const rSJ = @[ - byte 143, 55, 118, 73, 42, 115, 60, 77, - 95, 209, 41, 144, 250, 137, 138, 71, - 176, 242, 186, 232, 179, 30, 88, 255, - 198, 161, 182, 150, 220, 149, 33, 19 - ] - const sSJ = @[ - byte 213, 105, 105, 27, 129, 249, 139, 158, - 221, 68, 37, 163, 59, 71, 19, 108, - 60, 153, 183, 156, 25, 148, 37, 9, - 85, 205, 250, 246, 132, 142, 244, 36 - ] - - # construct the random element `r` from snarkjs "secret" r - let r = toFr[BN254_Snarks](rSJ) - # and `s` - let s = toFr[BN254_Snarks](sSJ) - - ctx.r = r - ctx.s = s - - let (A_p, B2_p, C_p) = ctx.prove() - - echo "\n==============================\n" - echo "A_p#16 = ", A_p.toHex() - echo "A_p#10 = ", A_p.toDecimal() - echo "------------------------------" - echo "B_p#16 = ", B2_p.toHex() - echo "B_p#10 = ", B2_p.toDecimal() - echo "------------------------------" - echo "C_p#16 = ", C_p.toHex() - echo "C_p#10 = ", C_p.toDecimal() - - ## SnarkJS yields: - ## - ## `snarkjs groth16 prove three_fac_final.zkey ../../circom/three_fac_js/witness.wtns proof.json public.json` - ## - #[ -{ - "pi_a": [ - "5525629793372463776337933283524928112323589665400780041477380790923758613749", - "21229177076048503863699135039723099340209138028149442778064006577287317302601", - "1" - ], - "pi_b": [ - [ - "10113559933709853115219982658131344715329670532374721861173670433756614595086", - "748111067660143353202076805159132563350177510079329482395824347599610874338" - ], - [ - "14193926223452546125681093394065339196897041249946578591171606543100010486627", - "871256420758854731396810855688710623510558493821614150596755347032202324148" - ], - [ - "1", - "0" - ] - ], - "pi_c": [ - "18517653609733492682442099361591955563405567929398531111532682405176646276349", - "17315036348446251361273519572420522936369550153340386126725970444173389652255", - "1" - ], - "protocol": "groth16", - "curve": "bn128" -} - ]# From aa114bb3111f637a59ecc5ddc051e2ac180fece6 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 13:44:12 +0100 Subject: [PATCH 42/49] [tests] clean up Groth16 test case, adjust for correct API --- tests/proof_systems/t_groth16_prover.nim | 45 ++++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/tests/proof_systems/t_groth16_prover.nim b/tests/proof_systems/t_groth16_prover.nim index 3e41addf1..e75b3b689 100644 --- a/tests/proof_systems/t_groth16_prover.nim +++ b/tests/proof_systems/t_groth16_prover.nim @@ -1,12 +1,39 @@ -import std/[os, unittest, strutils], - constantine/proof_systems/manual_groth16, +import std/[os, unittest, strutils, importutils], + constantine/proof_systems/groth16 {.all.}, # to call `calc*` procs constantine/named/algebras + + + #[ For information about the data files used in this test case, see `examples/groth16_prover.org`. ]# +proc proveManual[Name: static Algebra](ctx: var Groth16Prover[Name], + r, s: Fr[Name]): tuple[A: EC_ShortW_Aff[Fp[Name], G1], + B: EC_ShortW_Aff[Fp2[Name], G2], + C: EC_ShortW_Aff[Fp[Name], G1]] {.noinit.} = + ## Helper function for a "manual" Groth16 proof so that we can overwrite + ## the `r` and `s` parameters to compare with a SnarkJS proof. + ## + ## Identical implementation to `groth16.prove`, but sets `r` and `s` to inputs. + + # 1. Sample the random field elements `r` and `s` for the proof + privateAccess(ctx.type) + ctx.r = r + ctx.s = s + # 2. get the witness data needed for all proof elements + let wt = ctx.wtns.witnesses + # 3. compute the individual proof elements + let A_p = ctx.calcAp(wt) + let B2_p = ctx.calcBp(wt) + let B1_p = ctx.calcB1(wt) + let C_p = ctx.calcCp(A_p, B1_p, wt) + + result = (A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) + + suite "Groth16 prover": test "Proving 3-factorization example": const T = BN254_Snarks @@ -34,12 +61,6 @@ suite "Groth16 prover": let r = toFr[BN254_Snarks](rSJ) # and `s` let s = toFr[BN254_Snarks](sSJ) - # overwrite context's random values - ctx.r = r - ctx.s = s - - echo "r = ", ctx.r.toHex() - echo "s = ", ctx.s.toHex() # expected values produced by SnarkJS with these `r`, `s` values # x/y coordinates of Fp point on G1 subgroup of EC, corresponding to `g^A_1` @@ -77,7 +98,7 @@ suite "Groth16 prover": let cExp = toECG1(cx, cy) # call the proof and... - let (A_p, B2_p, C_p) = ctx.prove() + let (A_p, B2_p, C_p) = ctx.proveManual(r, s) echo aExp.toDecimal() echo bExp.toDecimal() @@ -93,12 +114,6 @@ suite "Groth16 prover": echo "C_p#16 = ", C_p.toHex() echo "C_p#10 = ", C_p.toDecimal() - #check (A_p == aExp.getJacobian).bool - #check (B2_p == bExp.getJacobian).bool - ### XXX: C currently fails! - #check (C_p == cExp.getJacobian).bool - check (A_p == aExp).bool check (B2_p == bExp).bool - ## XXX: C currently fails! check (C_p == cExp).bool From f1607f9ea2d0f859da2eed7e9f6cfb1ea452d1c8 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 13:59:36 +0100 Subject: [PATCH 43/49] remove duplicate `pow_vartime` due to rebase --- constantine/math/arithmetic/finite_fields.nim | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/constantine/math/arithmetic/finite_fields.nim b/constantine/math/arithmetic/finite_fields.nim index 3c1f07e8d..7e18eca2e 100644 --- a/constantine/math/arithmetic/finite_fields.nim +++ b/constantine/math/arithmetic/finite_fields.nim @@ -692,19 +692,6 @@ func pow_vartime*(r: var FF, a: FF, exponent: BigInt or openArray[byte] or FF) = # Small vartime exponentiation # ------------------------------------------------------------------- -func pow_vartime*(a: var FF, exponent: FF) = - ## Exponentiation modulo p - ## ``a``: a field element to be exponentiated - ## ``exponent``: a field element - ## - ## Warning ⚠️ : - ## This is an optimization for public exponent - ## Otherwise bits of the exponent can be retrieved with: - ## - memory access analysis - ## - power analysis - ## - timing analysis - a.pow_vartime(toBig exponent) - func pow_squareMultiply_vartime(a: var FF, exponent: SomeUnsignedInt) {.tags:[VarTime], meter.} = ## **Variable-time** Exponentiation ## From 1ea90dd6b9b471569ec83b119b52c24aaf0f1ba7 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 14:01:11 +0100 Subject: [PATCH 44/49] [tests] update ZKey test vectors from wrong `booldefine` branch Previously these were generated from the wrong branch of the `{.booldefine.}` variables we had in the code when the PR was still a draft. Instead of parsing the data as doubly Montgomery encoded as they actually are, we parsed them as regular Montgomery encoded resulting in wrong test vectors. --- tests/proof_systems/groth16_files/t_zkey.json | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/proof_systems/groth16_files/t_zkey.json b/tests/proof_systems/groth16_files/t_zkey.json index 9c170f3f4..2d97f3046 100644 --- a/tests/proof_systems/groth16_files/t_zkey.json +++ b/tests/proof_systems/groth16_files/t_zkey.json @@ -350,10 +350,10 @@ "value": { "mres": { "limbs": [ - 2893861064349225562, - 15291318972085769902, - 3172436813243865047, - 3336461168475855748 + 10902020042510041094, + 17381486299841078119, + 5900175412809962030, + 2475245527108272378 ] } } @@ -365,10 +365,10 @@ "value": { "mres": { "limbs": [ - 1997599621687373223, - 6052339484930628067, - 10108755138030829701, - 150537098327114917 + 12436184717236109307, + 3962172157175319849, + 7381016538464732718, + 1011752739694698287 ] } } @@ -380,10 +380,10 @@ "value": { "mres": { "limbs": [ - 2893861064349225562, - 15291318972085769902, - 3172436813243865047, - 3336461168475855748 + 10902020042510041094, + 17381486299841078119, + 5900175412809962030, + 2475245527108272378 ] } } @@ -395,10 +395,10 @@ "value": { "mres": { "limbs": [ - 1997599621687373223, - 6052339484930628067, - 10108755138030829701, - 150537098327114917 + 12436184717236109307, + 3962172157175319849, + 7381016538464732718, + 1011752739694698287 ] } } @@ -410,10 +410,10 @@ "value": { "mres": { "limbs": [ - 1997599621687373223, - 6052339484930628067, - 10108755138030829701, - 150537098327114917 + 12436184717236109307, + 3962172157175319849, + 7381016538464732718, + 1011752739694698287 ] } } @@ -425,10 +425,10 @@ "value": { "mres": { "limbs": [ - 1997599621687373223, - 6052339484930628067, - 10108755138030829701, - 150537098327114917 + 12436184717236109307, + 3962172157175319849, + 7381016538464732718, + 1011752739694698287 ] } } From bd59352433a3feefc7e9f05cc2829cce14fcfa44 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 14:03:17 +0100 Subject: [PATCH 45/49] [utils] add comment reference to double Montgomery encoding --- constantine/proof_systems/groth16_utils.nim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index 693830649..bf0628ce6 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -22,6 +22,12 @@ proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = result.fromBig(b) proc toFr*[Name: static Algebra](x: seq[byte], isMont = true, isDoubleMont = false): Fr[Name] = + ## Important note on `isDoubleMont`: + ## SnarkJS serializes the coefficients in the Zkey file as *doubly* Montgomery encoded values. + ## See here: + ## https://github.com/iden3/snarkjs/blob/master/src/zkey_new.js#L321-L335 + ## and here: + ## https://github.com/iden3/snarkjs/blob/master/src/zkey_new.js#L91 let b = matchingOrderBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian) if isMont: var bN: typeof(b) From c1f21f272eb9ed8e92dad1f722910e56d9bf8027 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 17:41:05 +0100 Subject: [PATCH 46/49] [tests] fix paths in Zkey and Groth16 prover tests --- tests/proof_systems/t_groth16_prover.nim | 8 ++++---- tests/proof_systems/t_zkey_parser.nim | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/proof_systems/t_groth16_prover.nim b/tests/proof_systems/t_groth16_prover.nim index e75b3b689..bbd4aef3c 100644 --- a/tests/proof_systems/t_groth16_prover.nim +++ b/tests/proof_systems/t_groth16_prover.nim @@ -3,7 +3,7 @@ import std/[os, unittest, strutils, importutils], constantine/named/algebras - +const TestDir = currentSourcePath.rsplit(DirSep, 1)[0] #[ For information about the data files used in this test case, see @@ -38,9 +38,9 @@ suite "Groth16 prover": test "Proving 3-factorization example": const T = BN254_Snarks # parse binary files - let wtns = parseWtnsFile("./groth16_files/witness.wtns").toWtns[:T]() - let zkey = parseZkeyFile("./groth16_files/three_fac_final.zkey").toZkey[:T]() - let r1cs = parseR1csFile("./groth16_files/three_fac.r1cs").toR1CS() + let wtns = parseWtnsFile(TestDir / "groth16_files/witness.wtns").toWtns[:T]() + let zkey = parseZkeyFile(TestDir / "groth16_files/three_fac_final.zkey").toZkey[:T]() + let r1cs = parseR1csFile(TestDir / "groth16_files/three_fac.r1cs").toR1CS() # construct mutable prover (to overwrite r, s) var ctx = Groth16Prover[T].init(zkey, wtns, r1cs) # definition of `r` and `s` values that produced expected proof diff --git a/tests/proof_systems/t_zkey_parser.nim b/tests/proof_systems/t_zkey_parser.nim index 2d8e867dc..87682003a 100644 --- a/tests/proof_systems/t_zkey_parser.nim +++ b/tests/proof_systems/t_zkey_parser.nim @@ -12,10 +12,10 @@ proc `%`(c: char): JsonNode = % ($c) proc `%`(c: SecretWord): JsonNode = % (c.uint64) const UpdateTestVectors = false -const RawVec = "groth16_files/t_zkey_bin.json" -const TypedVec = "groth16_files/t_zkey.json" - const TestDir = currentSourcePath.rsplit(DirSep, 1)[0] +const RawVec = TestDir / "groth16_files/t_zkey_bin.json" +const TypedVec = TestDir / "groth16_files/t_zkey.json" + suite "Zkey (.zkey) binary file parser": From 9a8a706e0eb5906852a884a9f7bc5384155b7c18 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 18:49:32 +0100 Subject: [PATCH 47/49] [groth16] add proper type for Groth16 proof --- constantine/proof_systems/groth16.nim | 14 +++++++++----- tests/proof_systems/t_groth16_prover.nim | 11 ++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/constantine/proof_systems/groth16.nim b/constantine/proof_systems/groth16.nim index d607b15fc..7e2a28921 100644 --- a/constantine/proof_systems/groth16.nim +++ b/constantine/proof_systems/groth16.nim @@ -24,7 +24,6 @@ export arithmetic, extension_fields, abstractions, type Groth16Prover*[Name: static Algebra] = object - ## XXX: In the future the below should be typed objects that are already unmarshalled! zkey*: Zkey[Name] wtns*: Wtns[Name] r1cs*: R1CS @@ -32,6 +31,12 @@ type r: Fr[Name] s: Fr[Name] + ## A type to hold the final Groth16 proof, `π(g₁^A, g₁^C, g₂^B)` + Groth16Proof*[Name: static Algebra] = object + A*: EC_ShortW_Aff[Fp[Name], G1] + B*: EC_ShortW_Aff[Fp2[Name], G2] + C*: EC_ShortW_Aff[Fp[Name], G1] + proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: Zkey[Name], wtns: Wtns[Name], r1cs: R1CS): Groth16Prover[Name] = result = Groth16Prover[Name]( zkey: zkey, @@ -221,9 +226,7 @@ proc calcCp[Name: static Algebra](ctx: Groth16Prover[Name], C_p = ctx.s * A_p + ctx.r * B1_p - (ctx.r * ctx.s) * delta1 + cw + resH result = C_p -proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_Aff[Fp[Name], G1], - B: EC_ShortW_Aff[Fp2[Name], G2], - C: EC_ShortW_Aff[Fp[Name], G1]] {.noinit.} = +proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): Groth16Proof {.noinit.} = ## Generate a proof given the Groth16 prover context data. ## ## This implies calculating the proof elements `π = (g₁^A, g₁^C, g₂^B)` @@ -240,4 +243,5 @@ proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): tuple[A: EC_ShortW_ let B1_p = ctx.calcB1(wt) let C_p = ctx.calcCp(A_p, B1_p, wt) - result = (A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) + result = Groth16Proof(A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) + diff --git a/tests/proof_systems/t_groth16_prover.nim b/tests/proof_systems/t_groth16_prover.nim index bbd4aef3c..3cdee8f8a 100644 --- a/tests/proof_systems/t_groth16_prover.nim +++ b/tests/proof_systems/t_groth16_prover.nim @@ -2,7 +2,6 @@ import std/[os, unittest, strutils, importutils], constantine/proof_systems/groth16 {.all.}, # to call `calc*` procs constantine/named/algebras - const TestDir = currentSourcePath.rsplit(DirSep, 1)[0] #[ @@ -11,9 +10,7 @@ For information about the data files used in this test case, see ]# proc proveManual[Name: static Algebra](ctx: var Groth16Prover[Name], - r, s: Fr[Name]): tuple[A: EC_ShortW_Aff[Fp[Name], G1], - B: EC_ShortW_Aff[Fp2[Name], G2], - C: EC_ShortW_Aff[Fp[Name], G1]] {.noinit.} = + r, s: Fr[Name]): Groth16Proof[Name] {.noinit.} = ## Helper function for a "manual" Groth16 proof so that we can overwrite ## the `r` and `s` parameters to compare with a SnarkJS proof. ## @@ -31,8 +28,7 @@ proc proveManual[Name: static Algebra](ctx: var Groth16Prover[Name], let B1_p = ctx.calcB1(wt) let C_p = ctx.calcCp(A_p, B1_p, wt) - result = (A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) - + result = Groth16Proof[Name](A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) suite "Groth16 prover": test "Proving 3-factorization example": @@ -98,7 +94,8 @@ suite "Groth16 prover": let cExp = toECG1(cx, cy) # call the proof and... - let (A_p, B2_p, C_p) = ctx.proveManual(r, s) + let proof = ctx.proveManual(r, s) # = ctx.proveManual(r, s) + let (A_p, B2_p, C_p) = (proof.A, proof.B, proof.C) echo aExp.toDecimal() echo bExp.toDecimal() From 4bfa1ebb592deff35beee01a0e87eb89ed99c957 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 18:50:10 +0100 Subject: [PATCH 48/49] [groth16] implement Groth16 proof verification and test it --- constantine/proof_systems/groth16.nim | 48 ++++++++++++++++++++++++ tests/proof_systems/t_groth16_prover.nim | 17 +++++++++ 2 files changed, 65 insertions(+) diff --git a/constantine/proof_systems/groth16.nim b/constantine/proof_systems/groth16.nim index 7e2a28921..2194041e5 100644 --- a/constantine/proof_systems/groth16.nim +++ b/constantine/proof_systems/groth16.nim @@ -7,6 +7,7 @@ import ../math/[arithmetic, extension_fields], ../platforms/abstractions, ../named/[algebras, properties_fields, properties_curves], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], + ../math/pairings/pairings_generic, ../named/zoo_generators import ../math/polynomials/[fft_fields, fft_lut] @@ -37,6 +38,14 @@ type B*: EC_ShortW_Aff[Fp2[Name], G2] C*: EC_ShortW_Aff[Fp[Name], G1] + ## The verification key to verify a Groth16 proof. + VerifyingKey*[Name: static Algebra] = object + alpha1*: EC_ShortW_Aff[Fp[Name], G1] ## g₁^α + beta2*: EC_ShortW_Aff[Fp2[Name], G2] ## g₂^β + gamma2*: EC_ShortW_Aff[Fp2[Name], G2] ## g₂^γ + delta2*: EC_ShortW_Aff[Fp2[Name], G2] ## g₂^δ + gamma_abc*: seq[EC_ShortW_Aff[Fp[Name], G1]] ## == `zkey.ic` == `[g₁^{ \frac{β·A_i(τ) + α·B_i(τ) + C_0(τ)}{ γ }}]` + proc init*[Name: static Algebra](G: typedesc[Groth16Prover[Name]], zkey: Zkey[Name], wtns: Wtns[Name], r1cs: R1CS): Groth16Prover[Name] = result = Groth16Prover[Name]( zkey: zkey, @@ -245,3 +254,42 @@ proc prove*[Name: static Algebra](ctx: Groth16Prover[Name]): Groth16Proof {.noin result = Groth16Proof(A: A_p.getAffine(), B: B2_p.getAffine(), C: C_p.getAffine()) +proc verify*[Name: static Algebra]( + vk: VerifyingKey[Name], + proof: Groth16Proof[Name], + publicInputs: seq[Fr[Name]] +): bool = + ## Verify a Groth16 proof using the provided verification key and public inputs + ## + ## This means checking the pairing equation: + ## `e(g₁^A, g₂^B) = e(g₁^α, g₂^β) · e(g₁^I, g₂^γ) · e(g₁^C, g₂^δ)` + ## where the left hand side is the pairing of two of the proof elements. + # 1. Check proof elements are valid curve points + let notOnCurve = + not isOnCurve(proof.A.x, proof.A.y, G1) or + not isOnCurve(proof.B.x, proof.B.y, G2) or + not isOnCurve(proof.C.x, proof.C.y, G1) + if bool(notOnCurve): + return false + elif publicInputs.len != vk.gamma_abc.len: # and inputs match + return false + + # 2. Compute `sum_pub = Σ ([publicInputs[i]] * gamma_abc[i]) = g₁^I` + var sum_pub_jac {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + sum_pub_jac.multiScalarMul_vartime(publicInputs, vk.gamma_abc) + let sum_pub = sum_pub_jac.getAffine() + + template pairing(x, y: untyped): untyped = # helper for cleaner code + var res {.noinit.}: Fp12[Name] + res.pairing(x, y) + res + + # 3. Compute pairings + let lhs = pairing(proof.A, proof.B) + + let rhs = pairing(vk.alpha1, vk.beta2) * + pairing(sum_pub, vk.gamma2) * + pairing(proof.C, vk.delta2) + + # 4. Check pairing equality + result = bool(lhs == rhs) diff --git a/tests/proof_systems/t_groth16_prover.nim b/tests/proof_systems/t_groth16_prover.nim index 3cdee8f8a..761c129fa 100644 --- a/tests/proof_systems/t_groth16_prover.nim +++ b/tests/proof_systems/t_groth16_prover.nim @@ -114,3 +114,20 @@ suite "Groth16 prover": check (A_p == aExp).bool check (B2_p == bExp).bool check (C_p == cExp).bool + + + ## Check verification passes + let wt = ctx.wtns.witnesses + + let g16h = ctx.zkey.g16h + let nPub = g16h.nVars.int - ctx.zkey.C.len + var pubInputs = newSeq[Fr[T]](nPub) + for i in 0 ..< nPub: + pubInputs[i] = wt[i] + + let vk = VerifyingKey[T](alpha1: g16h.alpha1, + beta2: g16h.beta2, + gamma2: g16h.gamma2, + delta2: g16h.delta2, + gamma_abc: zkey.ic) + check verify(vk, proof, pubInputs) From 8efc5bba54ebd21b768ee2c80148f644de4a5e68 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 25 Jan 2025 18:55:50 +0100 Subject: [PATCH 49/49] [groth16] remove the now unused CMM_WN booldefine --- constantine/proof_systems/groth16_utils.nim | 6 ------ 1 file changed, 6 deletions(-) diff --git a/constantine/proof_systems/groth16_utils.nim b/constantine/proof_systems/groth16_utils.nim index bf0628ce6..0d0472699 100644 --- a/constantine/proof_systems/groth16_utils.nim +++ b/constantine/proof_systems/groth16_utils.nim @@ -5,12 +5,6 @@ import ../math/[arithmetic, extension_fields], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul, ec_scalar_mul_vartime], ../csprngs/sysrand -## Read zkey coefficients `C` as double montgomery rep `MM`, witness data `W` in normal rep `N` -## This produces the correct 3 proof values -const CMM_WN* {.booldefine.} = false - -## What about `CN_WM`? - ## Helper constructors for Fp / Fr elements used in Groth16 binary file parsers. proc toFp*[Name: static Algebra](x: seq[byte], isMont = true): Fp[Name] = let b = matchingBigInt(Name).unmarshal(x.toOpenArray(0, x.len - 1), littleEndian)