Skip to content

Commit

Permalink
refactor(eth-verkle-ipa): transcript now use a cryptographic sponge-l…
Browse files Browse the repository at this point in the history
…ike API
  • Loading branch information
mratsim committed Jun 13, 2024
1 parent 05dae3b commit 931ebcb
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 273 deletions.
33 changes: 31 additions & 2 deletions constantine/commitments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ An important use-case missing from the Wikipedia article is:

"There exists a bundle of transactions that change the state of my database/ledger/blockchain to this state.". The whole bundle is not needed, only a short proof.

## KZG Polynomial Commitments
## KZG (Kate, Zaverucha, Goldberg)

- Constant-Size Commitments to Polynomials and Their Applications\
Kate, Zaverucha, Goldberg, 2010\
Expand All @@ -24,4 +24,33 @@ An important use-case missing from the Wikipedia article is:
- KZG Multiproofs
Feist, Khovratovich, 2020
https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html\
https://github.com/khovratovich/Kate/blob/master/Kate_amortized.pdf
https://github.com/khovratovich/Kate/blob/master/Kate_amortized.pdf

## Inner Product Arguments

- https://doc-internal.dalek.rs/bulletproofs/notes/inner_product_proof/index.html
- https://eprint.iacr.org/2019/1021
- https://zcash.github.io/halo2/background/pc-ipa.html
- https://eprint.iacr.org/2020/499
- https://dankradfeist.de/ethereum/2021/07/27/inner-product-arguments.html
- https://eprint.iacr.org/2023/691

## Transcripts

We take inspiration from

- https://merlin.cool/
https://github.com/dalek-cryptography/merlin
- https://github.com/crate-crypto/verkle-trie-ref/blob/master/ipa/transcript.py
- https://github.com/zcash/halo2/blob/halo2_proofs-0.3.0/halo2_proofs/src/transcript.rs
- https://github.com/arkworks-rs/poly-commit/blob/12f5529/poly-commit/src/ipa_pc/mod.rs#L34-L44
- https://eprint.iacr.org/2023/691

We MUST be compatible with `verkle-trie-ref` to be used in Ethereum Verkle Tries.

In summary, a transcript acts like a Cryptographic Sponge that can absorb entropy and squeeze out challenges.

However, even if we generalize the transcript API,
unfortunately the labeling differ (if any) and the absorb/challenge sequences and what is absorbed in the transcript are very different.

So the commitments have to be protocol-specific.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 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.

95 changes: 95 additions & 0 deletions constantine/commitments/eth_verkle_transcripts.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 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
../hashes,
../serialization/[endians, codecs_banderwagon],
../math/[arithmetic, ec_twistededwards],
../math/config/curves,
../math/io/io_bigints,
../math_arbitrary_precision/arithmetic/limbs_divmod_vartime,
../platforms/primitives

# ############################################################
#
# Transcripts
#
# ############################################################

# High-level transcripts designs
# - https://merlin.cool/
# https://github.com/dalek-cryptography/merlin
# - https://eprint.iacr.org/2023/691
#
# Practical implementations
# - https://github.com/crate-crypto/verkle-trie-ref/blob/master/ipa/transcript.py
# - https://github.com/zcash/halo2/blob/halo2_proofs-0.3.0/halo2_proofs/src/transcript.rs
# - https://github.com/arkworks-rs/poly-commit/blob/12f5529/poly-commit/src/ipa_pc/mod.rs#L34-L44
#
# Unfortunately, while the cryptographic sponge-like API to absorb_entropy / squeeze_challenge
# is shared, the actual usage differs significantly:
# - Ethereum verkle trie uses polynomial labels for domain separation
# - Halo2 has no labels
# - arkworks has no labels and does not absorb commitments
# https://github.com/arkworks-rs/poly-commit/issues/140
# which likely exposes it to Weak Fiat-Shamir attacks.

func initTranscript*(ctx: var CryptoHash, label: openArray[char]) =
ctx.init()
ctx.update(label)

func domainSeparator*(ctx: var CryptoHash, label: openArray[char]) =
# A domain separator is used to:
# - Separate between adding elements to the transcript and squeezing elements out
# - Separate sub-protocols
ctx.update(label)

func absorb*(ctx: var CryptoHash, label: openArray[char], message: openArray[byte]) =
ctx.update(label)
ctx.update(message)

func absorb*(ctx: var CryptoHash, label: openArray[char], v: uint64) =
ctx.update(label)
ctx.update(v.toBytes(bigEndian))

func absorb*(ctx: var CryptoHash, label: openArray[char], point: ECP_TwEdwards[Fp[Banderwagon]]) =
var bytes {.noInit.}: array[32, byte]
bytes.serialize(point)
ctx.absorb(label, bytes)

func absorb*(ctx: var CryptoHash, label: openArray[char], scalar: Fr[Banderwagon]) =
var bytes {.noInit.}: array[32, byte]
bytes.serialize_fr(scalar, littleEndian)
ctx.absorb(label, bytes)

func absorb(ctx: var CryptoHash, label: openArray[char], scalar: matchingOrderBigInt(Banderwagon)) =
var bytes {.noInit.}: array[32, byte]
bytes.serialize_scalar(scalar, littleEndian)
ctx.absorb(label, bytes)

func squeezeChallenge*(ctx: var CryptoHash, label: openArray[char], challenge: var matchingOrderBigInt(Banderwagon)) =
## Generating a challenge based on the Fiat-Shamir transform
ctx.domainSeparator(label)

# Finalise the transcript state into a hash
var digest {.noInit.}: array[32, byte]
ctx.finish(digest)

var big {.noInit.}: BigInt[32*8]
big.unmarshal(digest, littleEndian)
discard challenge.reduce_vartime(big, Fr[Banderwagon].fieldMod())

# Reset the Transcript state & absorb the freshly generated challenge
ctx.init()
ctx.absorb(label, challenge)

func squeezeChallenge*(ctx: var CryptoHash, label: openArray[char], challenge: var Fr[Banderwagon]) =
## Generating a challenge based on the Fiat-Shamir transform
var big {.noInit.}: matchingOrderBigInt(Banderwagon)
ctx.squeezeChallenge(label, big)
challenge.fromBig(big)
25 changes: 12 additions & 13 deletions constantine/eth_verkle_ipa/ipa_prover.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
# ############################################################

import
./[transcript_gen, common_utils, eth_verkle_constants],
./[common_utils, eth_verkle_constants],
../platforms/primitives,
../hashes,
../serialization/[
codecs_banderwagon,
codecs_status_codes,
],
../commitments/pedersen_commitments,
../commitments/[
pedersen_commitments,
eth_verkle_transcripts],
../math/config/[type_ff, curves],
../math/elliptic/[ec_twistededwards_affine, ec_twistededwards_projective],
../math/polynomials/polynomials,
Expand Down Expand Up @@ -103,14 +105,11 @@ func coverIPARounds*(
res.R_vector[idx] = C_R
idx = idx + 1

transcript.pointAppend(asBytes"L", C_L)
transcript.pointAppend(asBytes"R", C_R)

var x_big: matchingOrderBigInt(Banderwagon)
x_big.generateChallengeScalar(transcript, asBytes"x")
transcript.absorb("L", C_L)
transcript.absorb("R", C_R)

var x {.noInit.}: Fr[Banderwagon]
x.fromBig(x_big)
transcript.squeezeChallenge("x", x)

var xInv {.noInit.}: Fr[Banderwagon]
xInv.inv(x)
Expand Down Expand Up @@ -143,19 +142,19 @@ func createIPAProof*[IPAProof](
# TODO: for some result IPAProof must be zero-init beforehand
# hence we need to investigate why initialization may be incomplete.

transcript.domain_separator(asBytes"ipa")
transcript.domain_separator("ipa")
var b: array[EthVerkleDomain, Fr[Banderwagon]]
ic.domain.getLagrangeBasisPolysAt(b, evalPoint)

var innerProd {.noInit.}: Fr[Banderwagon]
innerProd.computeInnerProducts(a, b)

transcript.pointAppend(asBytes"C", commitment)
transcript.scalarAppend(asBytes"input point", evalPoint.toBig())
transcript.scalarAppend(asBytes"output point", innerProd.toBig())
transcript.absorb("C", commitment)
transcript.absorb("input point", evalPoint)
transcript.absorb("output point", innerProd)

var w {.noInit.}: matchingOrderBigInt(Banderwagon)
w.generateChallengeScalar(transcript, asBytes"w")
transcript.squeezeChallenge("w", w)

var q {.noInit.}: ECP_TwEdwards_Prj[Fp[Banderwagon]]
q.fromAffine(Banderwagon.getGenerator())
Expand Down
31 changes: 13 additions & 18 deletions constantine/eth_verkle_ipa/ipa_verifier.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
./[transcript_gen, common_utils, eth_verkle_constants, ipa_prover],
./[common_utils, eth_verkle_constants, ipa_prover],
../platforms/primitives,
../math/config/[type_ff, curves],
../hashes,
Expand All @@ -25,34 +25,31 @@ import
# ############################################################

func generateChallengesForIPA*(res: var openArray[matchingOrderBigInt(Banderwagon)], transcript: var CryptoHash, proof: IPAProof) =
for i in 0 ..< 8:
transcript.pointAppend( asBytes"L", proof.L_vector[i])
transcript.pointAppend( asBytes"R", proof.R_vector[i])
res[i].generateChallengeScalar(transcript,asBytes"x")
for i in 0 ..< 8: # TODO 8 is hardcoded
transcript.absorb("L", proof.L_vector[i])
transcript.absorb("R", proof.R_vector[i])
transcript.squeezeChallenge("x", res[i])

func checkIPAProof* (ic: IPASettings, transcript: var CryptoHash, got: var EC_P, commitment: var EC_P, proof: IPAProof, evalPoint: Fr[Banderwagon], res: Fr[Banderwagon]) : bool =
# Check IPA proof verifier a IPA proof for a committed polynomial in evaluation form
# It verifies whether the proof is valid for the given polynomial at the evaluation `evalPoint`
# and cross-checking it with `result`
var r {.noInit.} : bool

transcript.domain_separator(asBytes"ipa")
transcript.domain_separator("ipa")

debug: doAssert (proof.L_vector.len == proof.R_vector.len), "Proof lengths unequal!"

debug: doAssert (proof.L_vector.len == int(ic.numRounds)), "Proof length and num round unequal!"


var b {.noInit.}: array[EthVerkleDomain, Fr[Banderwagon]]
# b.computeBarycentricCoefficients(ic.precompWeights,evalPoint)
ic.domain.getLagrangeBasisPolysAt(b, evalPoint)

transcript.pointAppend(asBytes"C", commitment)
transcript.scalarAppend(asBytes"input point", evalPoint.toBig())
transcript.scalarAppend(asBytes"output point", res.toBig())
transcript.absorb("C", commitment)
transcript.absorb("input point", evalPoint)
transcript.absorb("output point", res)

var w : matchingOrderBigInt(Banderwagon)
w.generateChallengeScalar(transcript,asBytes"w")
transcript.squeezeChallenge("w", w)

# Rescaling of q read https://hackmd.io/mJeCRcawTRqr9BooVpHv5g#Re-defining-the-quotient
var q {.noInit.}: ECP_TwEdwards_Prj[Fp[Banderwagon]]
Expand All @@ -63,13 +60,11 @@ func checkIPAProof* (ic: IPASettings, transcript: var CryptoHash, got: var EC_P,
qy.scalarMul_vartime(res)
commitment += qy


var challenges_big {.noInit.}: array[8, matchingOrderBigInt(Banderwagon)]
challenges_big.generateChallengesForIPA(transcript, proof)

var challenges {.noInit.}: array[8,Fr[Banderwagon]]
for i in 0 ..< 8:
challenges[i].fromBig(challenges_big[i])
transcript.absorb("L", proof.L_vector[i])
transcript.absorb("R", proof.R_vector[i])
transcript.squeezeChallenge("x", challenges[i])

var challengesInv {.noInit.}: array[8,Fr[Banderwagon]]
challengesInv.batchInv_vartime(challenges)
Expand Down
Loading

0 comments on commit 931ebcb

Please sign in to comment.