diff --git a/CHANGELOG.md b/CHANGELOG.md
index 985ca384d3..63a6b7b54e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,247 @@
+
+## [v0.11.0] - 2024-09-06
+### Build
+- update runner and go version ([#1260](https://github.com/consensys/gnark/issues/1260))
+
+### Chore
+- update version
+- explicit IO methods in interfaces ([#1266](https://github.com/consensys/gnark/issues/1266))
+- update gnark-crypto module
+- clean old definition
+- generate
+- define interface for solidity-supported VK
+- make function comments match function names ([#1163](https://github.com/consensys/gnark/issues/1163))
+
+### Docs
+- update README ([#1255](https://github.com/consensys/gnark/issues/1255))
+- update reference
+- describe potential length extension attack when using MiMC in-circuit ([#1198](https://github.com/consensys/gnark/issues/1198))
+- fix typo in package doc
+- add documentation
+- update documentation for ecdsa and eddsa
+- update TODOs ([#1109](https://github.com/consensys/gnark/issues/1109))
+
+### Feat
+- use offset variable in derive_gamma
+- handle invalid signature failures in ECRecover precompile ([#1101](https://github.com/consensys/gnark/issues/1101))
+- allow configurable hash-to-field function for Groth16 Solidity verifier ([#1102](https://github.com/consensys/gnark/issues/1102))
+- add IsOnG2 for BN254 ([#1204](https://github.com/consensys/gnark/issues/1204))
+- use blake2 for variable hashcode ([#1197](https://github.com/consensys/gnark/issues/1197))
+- simplified offset computation
+- ensure verifying keys implement Solidity interface
+- handle solidity options in CI check
+- use fpstr in Groth16 Solidity template
+- define fpstr method for groth16 solidity template
+- define import_fp template
+- code gen
+- statistical zero knowledge option for bn254
+- use offset in pairing
+- capture O variable in gate for external range checker ([#1211](https://github.com/consensys/gnark/issues/1211))
+- code gen
+- add BN254 final exponentiation check with output ([#1209](https://github.com/consensys/gnark/issues/1209))
+- use anonymous struct
+- added version solidity template groth16 verifier
+- re ran code gen and add files
+- update solidity template groth16
+- update template
+- change signature ExportSolidity groth16
+- code gen
+- code gen
+- modified plonk template
+- code gen
+- modified signature of ExportSolidity in template
+- addition of export options for solidity verifiers
+- simplify loop constraint index
+- **bw6:** Fp6 as a direct extension using Montgomery-6
+
+### Feat
+- implement FixedLengthSum of sha2 ([#821](https://github.com/consensys/gnark/issues/821))
+
+### Fix
+- remove redundant select
+- variable modulus subtraction padding ([#1200](https://github.com/consensys/gnark/issues/1200))
+- branch with unchecked cast could panic at compile time ([#1234](https://github.com/consensys/gnark/issues/1234))
+- minimum 1 bit for constant binary decomposition ([#1229](https://github.com/consensys/gnark/issues/1229))
+- edge case with PLONK backend when 1 constraint ([#1226](https://github.com/consensys/gnark/issues/1226))
+- strict ModReduce in emulated fields ([#1224](https://github.com/consensys/gnark/issues/1224))
+- used size in domain instead of recomputing it
+- remove unconstrained and unused variables
+- remove unconstrained and unused variables ([#1218](https://github.com/consensys/gnark/issues/1218))
+- avoid malicious hint in BLS12-381 final exp check
+- avoid infinite loop in hint when previous ML=0
+- avoid malicious hint in BN254 final exp check
+- conditional check in non-native IsZero for applying optimization ([#1145](https://github.com/consensys/gnark/issues/1145))
+- use consecutive powers instead of squaring ([#1212](https://github.com/consensys/gnark/issues/1212))
+- use emulated arithmetic for GLV decomp ([#1167](https://github.com/consensys/gnark/issues/1167))
+- restored cfg struct
+- fixed error_mod_exp comment
+- shift constraint indices by nb of public vars ([#1128](https://github.com/consensys/gnark/issues/1128))
+- fixed conflicts
+- fixed comment prove
+- fixed typo PROOF_H_0_X -> PROOF_H_0_COM_X
+- fixed UnmarshalSolidity
+- fixed comment
+- fixed fold_h comment
+- fixed comment
+- fixed comment
+- removed redundant computation pointer update
+- remove redundant computation
+- several external typo fixes ([#1261](https://github.com/consensys/gnark/issues/1261))
+- ensure condition is bool in api.Select
+- fix OR computation in case one input is constant and other variable ([#1181](https://github.com/consensys/gnark/issues/1181))
+- fixed comment unmarshal
+- fixed comment solidity
+- fixed comment solidity
+- fixed comment solidity
+- fixed comment solidity
+- fixed some comments
+- fixed L-05
+- fixed L-04
+- fixed M-04
+- fixed L-04
+- fixed l-02
+- defer to math.bits when nbdigits big or not set
+- fix [#1149](https://github.com/consensys/gnark/issues/1149) by removing unused code ([#1164](https://github.com/consensys/gnark/issues/1164))
+- exact width for upper part
+- more descriptive error message ([#1104](https://github.com/consensys/gnark/issues/1104))
+- added missing import in template
+- fixed solidity template
+- fixes [#1157](https://github.com/consensys/gnark/issues/1157) ensures calls to AttachDebugInfo are surrounded with… ([#1160](https://github.com/consensys/gnark/issues/1160))
+- **bls12-377:** use FinalExponentiationCheck in pairing2.go
+- **bls12-377:** push to cyclo group in pairing2.go
+- **bls12-377:** hint computation for final exp
+- **bls12-377:** naming of Fp6 mul
+- **bw6:** Toom-Cook 6-way mul
+- **bw6:** pairing using direct sextic extension
+- **pairing:** fix benchmarks
+- **uints:** constrain valueOf ([#1139](https://github.com/consensys/gnark/issues/1139))
+
+### Fix
+- Build on 32-bit arch would raise int overflow https://github.com/Consensys/gnark/issues/1192 ([#1195](https://github.com/consensys/gnark/issues/1195))
+
+### Perf
+- eliminate final exp in bls12-381 optimized
+- eliminate final exp in bls12-377
+- optimize final exp bls12-377
+- **bls12-381:** use cyclotomic group in finel exp check
+- **bls12-381:** eliminate finalexp ~naively
+- **bls12-381:** revisit tower extension
+- **bn254:** eliminate finalexp as per eprint 2024/640
+- **bn254:** use cyclotomic group in final exp check
+- **bn254:** revisit tower extension
+- **bn254:** save one mul in finalExp check
+- **bn254:** optimize addchain for exp by 6u+2
+- **bn254:** eliminate finalexp in precompile
+- **bw6:** optimize specialized Montgomery-6 mul
+- **bw6:** sparse mul by lines
+- **bw6:** save 2 subs in fp6 sq
+- **bw6:** optimize mulby023
+- **bw6:** mulby02345
+- **bw6:** save some subs in Fp6 square
+- **bw6:** Square uses Karatsuba over Chung-Hasan instead of TC6
+- **bw6:** revisit tower extension
+- **bw6:** save some adds in specialized mul e6
+- **bw6:** use hint to divide by 362880 in Toom-6
+- **bw6:** optimize Montgomery-6 mul
+- **bw6:** optimize pairing with new tower
+- **bw6:** use Karabina12345 instead of GS for small sizes too
+- **bw6:** toom-cook-3x for Fp3 mul
+- **bw6-761:** eliminate finalexp
+- **bw6-761:** use Karabina even for 1 square
+- **bw6-761:** push ML to cyclo-group before FE elimination
+
+### Refactor
+- clean code
+- revert to old line computation and adjust gnark-crypto instead
+- move utils from std/ to internal
+- apply review suggestions
+- apply review suggestions
+- separate fixed circuits used in zkevm ([#1217](https://github.com/consensys/gnark/issues/1217))
+- clean code
+- clean code
+- move limb composition to package
+- use single implementation
+- clean code
+- removes todods ([#1111](https://github.com/consensys/gnark/issues/1111))
+- **bls12-377:** karabina decompression
+- **bn254:** clean FE elimination code
+- **bn254:** add some comments
+- **bw6:** remove dead code
+- **bw6:** remove benchmark
+- **bw6:** remove benchmark
+- **bw6:** apply review suggestion
+
+### Style
+- fixed wrong EcMul comment
+- fixed comment
+- fixed comment
+- fixed typos
+- added comment
+- removed unused variable
+- constant for fixed size proof
+- SHA2 constant to replace 0x2
+- cleaner computation nb BSB commitments
+- renamed H commitments constants
+- reuse variable
+- removed redundant if statement
+- lagrangeOne -> lagrangeZero
+- verify_opening_linearised_polynomial -> compute_opening_linearised_polynomial
+- divideByXMinusOne could -> divideByZH
+- fixed typo
+- clean code
+- remove old todos ([#1106](https://github.com/consensys/gnark/issues/1106))
+
+### Test
+- update stats
+- update stats
+- added non regression test for api.Select bool cond
+- add issue 1153 repro
+- update stats
+- check errors in test circuit ([#1140](https://github.com/consensys/gnark/issues/1140))
+- update stats
+- update stats
+- update stats
+- update stats
+- add PLONK test for public input mapping ([#1123](https://github.com/consensys/gnark/issues/1123))
+- update stats
+
+### Wip
+- toom-cook-3x
+
+### Pull Requests
+- Merge pull request [#1254](https://github.com/consensys/gnark/issues/1254) from Consensys/perf/ML
+- Merge pull request [#1258](https://github.com/consensys/gnark/issues/1258) from Consensys/refactor/limb-composition
+- Merge pull request [#1251](https://github.com/consensys/gnark/issues/1251) from Consensys/build/bump-gnarkcrypto-vsn
+- Merge pull request [#1247](https://github.com/consensys/gnark/issues/1247) from Consensys/fix/issue1246
+- Merge pull request [#1207](https://github.com/consensys/gnark/issues/1207) from Consensys/perf/eliminate-finalExp-bls
+- Merge pull request [#1214](https://github.com/consensys/gnark/issues/1214) from Consensys/fix/BN254-finalExp
+- Merge pull request [#1196](https://github.com/consensys/gnark/issues/1196) from ThomasPiellard/audit/final-commit-fixes
+- Merge pull request [#1143](https://github.com/consensys/gnark/issues/1143) from Consensys/perf/eliminate-finalExp
+- Merge pull request [#1187](https://github.com/consensys/gnark/issues/1187) from Consensys/fix/groth16-solidity-templates
+- Merge pull request [#1155](https://github.com/consensys/gnark/issues/1155) from Consensys/perf/eliminate-finalExp-bw6761
+- Merge pull request [#1173](https://github.com/consensys/gnark/issues/1173) from Consensys/perf/eliminate-finalExp-bls12381
+- Merge pull request [#11](https://github.com/consensys/gnark/issues/11) from ThomasPiellard/audit/M-04
+- Merge pull request [#10](https://github.com/consensys/gnark/issues/10) from ThomasPiellard/audit/L-02
+- Merge pull request [#14](https://github.com/consensys/gnark/issues/14) from ThomasPiellard/audit/H-01
+- Merge pull request [#13](https://github.com/consensys/gnark/issues/13) from ThomasPiellard/audit/N-05
+- Merge pull request [#2](https://github.com/consensys/gnark/issues/2) from ThomasPiellard/audit/N-01
+- Merge pull request [#5](https://github.com/consensys/gnark/issues/5) from ThomasPiellard/audit/L-08
+- Merge pull request [#7](https://github.com/consensys/gnark/issues/7) from ThomasPiellard/audit/L-06
+- Merge pull request [#6](https://github.com/consensys/gnark/issues/6) from ThomasPiellard/audit/L-07
+- Merge pull request [#8](https://github.com/consensys/gnark/issues/8) from ThomasPiellard/audit/L-05
+- Merge pull request [#4](https://github.com/consensys/gnark/issues/4) from ThomasPiellard/audit/N-03
+- Merge pull request [#3](https://github.com/consensys/gnark/issues/3) from ThomasPiellard/audit/N-02
+- Merge pull request [#1](https://github.com/consensys/gnark/issues/1) from ThomasPiellard/audit/N-04
+- Merge pull request [#9](https://github.com/consensys/gnark/issues/9) from ThomasPiellard/audit/L-04
+- Merge pull request [#12](https://github.com/consensys/gnark/issues/12) from ThomasPiellard/audit/L-03
+- Merge pull request [#1165](https://github.com/consensys/gnark/issues/1165) from Consensys/fix/partition-bounds
+- Merge pull request [#1138](https://github.com/consensys/gnark/issues/1138) from Consensys/feat/option_solidity
+- Merge pull request [#1131](https://github.com/consensys/gnark/issues/1131) from Consensys/perf/toom3-r1cs
+- Merge pull request [#1126](https://github.com/consensys/gnark/issues/1126) from Consensys/perf/direct-extensions
+- Merge pull request [#1110](https://github.com/consensys/gnark/issues/1110) from Consensys/perf/field-extensions
+- Merge pull request [#1113](https://github.com/consensys/gnark/issues/1113) from Consensys/docs/signatures
+
+
## [v0.10.0] - 2024-04-22
### Bench
@@ -2955,6 +3199,7 @@
- Merge pull request [#5](https://github.com/consensys/gnark/issues/5) from ConsenSys/go1.14_deps
+[v0.11.0]: https://github.com/consensys/gnark/compare/v0.10.0...v0.11.0
[v0.10.0]: https://github.com/consensys/gnark/compare/v0.9.1...v0.10.0
[v0.9.1]: https://github.com/consensys/gnark/compare/v0.9.0...v0.9.1
[v0.9.0]: https://github.com/consensys/gnark/compare/v0.9.0-alpha...v0.9.0
diff --git a/README.md b/README.md
index 0b478b6e94..351ac156f3 100644
--- a/README.md
+++ b/README.md
@@ -191,17 +191,17 @@ If you use `gnark` in your research a citation would be appreciated.
Please use the following BibTeX to cite the most recent release.
```bib
-@software{gnark-v0.10.0,
+@software{gnark-v0.11.0,
author = {Gautam Botrel and
Thomas Piellard and
Youssef El Housni and
Ivo Kubjas and
Arya Tabaie},
- title = {ConsenSys/gnark: v0.10.0},
- month = apr,
+ title = {ConsenSys/gnark: v0.11.0},
+ month = sep,
year = 2024,
publisher = {Zenodo},
- version = {v0.10.0},
+ version = {v0.11.0},
doi = {10.5281/zenodo.5819104},
url = {https://doi.org/10.5281/zenodo.5819104}
}
diff --git a/backend/groth16/bellman_test.go b/backend/groth16/bellman_test.go
index f8bef569c8..b47a6244c6 100644
--- a/backend/groth16/bellman_test.go
+++ b/backend/groth16/bellman_test.go
@@ -24,62 +24,62 @@ func TestVerifyBellmanProof(t *testing.T) {
ok bool
}{
{
- "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",
+ "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",
true,
},
{
- "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",
+ "hwk883gUlTKCyXYA6XWZa8H9/xKIYZaJ0xEs0M5hQOMxiGpxocuX/8maSDmeCk3bhwk883gUlTKCyXYA6XWZa8H9/xKIYZaJ0xEs0M5hQOMxiGpxocuX/8maSDmeCk3bo5ViaDBdO7ZBxAhLSe5k/5TFQyF5Lv7KN2tLKnwgoWMqB16OL8WdbePIwTCuPtJNAFKoTZylLDbSf02kckMcZQDPF9iGh+JC99Pio74vDpwTEjUx5tQ99gNQwxULtztsqDRsPnEvKvLmsxHt8LQVBkEBm2PBJFY+OXf1MNW021viDBpR10mX4WQ6zrsGL5L0GY4cwf4tlbh+Obit+LnN/SQTnREf8fPpdKZ1sa/ui3pGi8lMT6io4D7Ujlwx2RdChwk883gUlTKCyXYA6XWZa8H9/xKIYZaJ0xEs0M5hQOMxiGpxocuX/8maSDmeCk3bkBF+isfMf77HCEGsZANw0hSrO2FGg14Sl26xLAIohdaW8O7gEaag8JdVAZ3OVLd5Df1NkZBEr753Xb8WwaXsJjE7qxwINL1KdqA4+EiYW4edb7+a9bbBeOPtb67ZxmFqAAAAAoMkzUv+KG8WoXszZI5NNMrbMLBDYP/xHunVgSWcix/kBrGlNozv1uFr0cmYZiij3YqToYs+EZa3dl2ILHx7H1n+b+Bjky/td2QduHVtf5t/Z9sKCfr+vOn12zVvOVz/6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"lvQLU/KqgFhsLkt/5C/scqs7nWR+eYtyPdWiLVBux9GblT4AhHYMdCgwQfSJcudvsgV6fXoK+DUSRgJ++Nqt+Wvb7GlYlHpxCysQhz26TTu8Nyo7zpmVPH92+UYmbvbQCSvX2BhWtvkfHmqDVjmSIQ4RUMfeveA1KZbSf999NE4qKK8Do+8oXcmTM4LZVmh1rlyqznIdFXPN7x3pD4E0gb6/y69xtWMChv9654FMg05bAdueKt9uA4BEcAbpkdHF", "cmzVCcRVnckw3QUPhmG4Bkppeg4K50oDQwQ9EH+Fq1s=",
false,
},
{
- "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",
+ "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",
false,
},
{
- "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",
+ "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", "",
true,
},
{
- "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",
+ "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",
true,
},
{
- "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",
+ "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",
true,
},
{
- "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",
+ "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",
true,
},
{
- "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",
+ "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",
true,
},
{
- "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",
+ "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", "",
false,
},
{
- "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",
+ "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",
false,
},
{
- "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",
+ "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",
false,
},
{
- "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",
+ "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",
false,
},
diff --git a/backend/groth16/bls12-377/commitment_test.go b/backend/groth16/bls12-377/commitment_test.go
index f18eefb837..ddc5a295bd 100644
--- a/backend/groth16/bls12-377/commitment_test.go
+++ b/backend/groth16/bls12-377/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bls12-377/marshal.go b/backend/groth16/bls12-377/marshal.go
index ee8facd1e6..d0e0f295c8 100644
--- a/backend/groth16/bls12-377/marshal.go
+++ b/backend/groth16/bls12-377/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bls12-377/marshal_test.go b/backend/groth16/bls12-377/marshal_test.go
index 2abaf23406..1859b21e08 100644
--- a/backend/groth16/bls12-377/marshal_test.go
+++ b/backend/groth16/bls12-377/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bls12-377/prove.go b/backend/groth16/bls12-377/prove.go
index e79d9e835e..47c715ada3 100644
--- a/backend/groth16/bls12-377/prove.go
+++ b/backend/groth16/bls12-377/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr"
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bls12-377/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bls12-377/setup.go b/backend/groth16/bls12-377/setup.go
index 97c88ba3ce..5b6b3af78d 100644
--- a/backend/groth16/bls12-377/setup.go
+++ b/backend/groth16/bls12-377/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bls12-377/verify.go b/backend/groth16/bls12-377/verify.go
index 03da861b2f..2a09746de9 100644
--- a/backend/groth16/bls12-377/verify.go
+++ b/backend/groth16/bls12-377/verify.go
@@ -98,14 +98,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
diff --git a/backend/groth16/bls12-381/commitment_test.go b/backend/groth16/bls12-381/commitment_test.go
index 8f6882aeb1..150dcccea6 100644
--- a/backend/groth16/bls12-381/commitment_test.go
+++ b/backend/groth16/bls12-381/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bls12-381/marshal.go b/backend/groth16/bls12-381/marshal.go
index ff47b52ad4..8a34d864fd 100644
--- a/backend/groth16/bls12-381/marshal.go
+++ b/backend/groth16/bls12-381/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bls12-381/marshal_test.go b/backend/groth16/bls12-381/marshal_test.go
index 32201fdfcc..83b2008995 100644
--- a/backend/groth16/bls12-381/marshal_test.go
+++ b/backend/groth16/bls12-381/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bls12-381/prove.go b/backend/groth16/bls12-381/prove.go
index 37dd0a0d50..671092603e 100644
--- a/backend/groth16/bls12-381/prove.go
+++ b/backend/groth16/bls12-381/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bls12-381/setup.go b/backend/groth16/bls12-381/setup.go
index caf1e9d48b..5c2f198ff9 100644
--- a/backend/groth16/bls12-381/setup.go
+++ b/backend/groth16/bls12-381/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bls12-381/verify.go b/backend/groth16/bls12-381/verify.go
index 39d3ba6432..fc7f40bfcc 100644
--- a/backend/groth16/bls12-381/verify.go
+++ b/backend/groth16/bls12-381/verify.go
@@ -98,14 +98,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
diff --git a/backend/groth16/bls24-315/commitment_test.go b/backend/groth16/bls24-315/commitment_test.go
index 0f626448b3..66bc8e7ebf 100644
--- a/backend/groth16/bls24-315/commitment_test.go
+++ b/backend/groth16/bls24-315/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bls24-315/marshal.go b/backend/groth16/bls24-315/marshal.go
index 614647c775..32cbca8368 100644
--- a/backend/groth16/bls24-315/marshal.go
+++ b/backend/groth16/bls24-315/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bls24-315/marshal_test.go b/backend/groth16/bls24-315/marshal_test.go
index c4115cacea..d7e5e2d933 100644
--- a/backend/groth16/bls24-315/marshal_test.go
+++ b/backend/groth16/bls24-315/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bls24-315/prove.go b/backend/groth16/bls24-315/prove.go
index e3c3e560a7..cb846a9833 100644
--- a/backend/groth16/bls24-315/prove.go
+++ b/backend/groth16/bls24-315/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bls24-315/fr"
"github.com/consensys/gnark-crypto/ecc/bls24-315/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bls24-315/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bls24-315/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bls24-315/setup.go b/backend/groth16/bls24-315/setup.go
index 19f1ed2e3d..6b3f193999 100644
--- a/backend/groth16/bls24-315/setup.go
+++ b/backend/groth16/bls24-315/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bls24-315/verify.go b/backend/groth16/bls24-315/verify.go
index 28a66d60ca..7680a54d1f 100644
--- a/backend/groth16/bls24-315/verify.go
+++ b/backend/groth16/bls24-315/verify.go
@@ -98,14 +98,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
diff --git a/backend/groth16/bls24-317/commitment_test.go b/backend/groth16/bls24-317/commitment_test.go
index bfb2fc578e..99a80407ea 100644
--- a/backend/groth16/bls24-317/commitment_test.go
+++ b/backend/groth16/bls24-317/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bls24-317/marshal.go b/backend/groth16/bls24-317/marshal.go
index 00e0222422..c1aa622e0c 100644
--- a/backend/groth16/bls24-317/marshal.go
+++ b/backend/groth16/bls24-317/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bls24-317/marshal_test.go b/backend/groth16/bls24-317/marshal_test.go
index e926a1b459..b105bc2181 100644
--- a/backend/groth16/bls24-317/marshal_test.go
+++ b/backend/groth16/bls24-317/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bls24-317/prove.go b/backend/groth16/bls24-317/prove.go
index 59651832cb..55a4a39a14 100644
--- a/backend/groth16/bls24-317/prove.go
+++ b/backend/groth16/bls24-317/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bls24-317/fr"
"github.com/consensys/gnark-crypto/ecc/bls24-317/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bls24-317/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bls24-317/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bls24-317/setup.go b/backend/groth16/bls24-317/setup.go
index 7a051e02db..53628f4c5e 100644
--- a/backend/groth16/bls24-317/setup.go
+++ b/backend/groth16/bls24-317/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bls24-317/verify.go b/backend/groth16/bls24-317/verify.go
index de18d44097..d01ad203e9 100644
--- a/backend/groth16/bls24-317/verify.go
+++ b/backend/groth16/bls24-317/verify.go
@@ -98,14 +98,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
diff --git a/backend/groth16/bn254/commitment_test.go b/backend/groth16/bn254/commitment_test.go
index 759501ebe2..9f29b5d3bb 100644
--- a/backend/groth16/bn254/commitment_test.go
+++ b/backend/groth16/bn254/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bn254/marshal.go b/backend/groth16/bn254/marshal.go
index 90dd38d8ad..d1c6ba186f 100644
--- a/backend/groth16/bn254/marshal.go
+++ b/backend/groth16/bn254/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bn254/marshal_test.go b/backend/groth16/bn254/marshal_test.go
index a62d4297f5..d59c54e85f 100644
--- a/backend/groth16/bn254/marshal_test.go
+++ b/backend/groth16/bn254/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bn254/prove.go b/backend/groth16/bn254/prove.go
index b9dd9f0cdf..5f0d413133 100644
--- a/backend/groth16/bn254/prove.go
+++ b/backend/groth16/bn254/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/consensys/gnark-crypto/ecc/bn254/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bn254/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bn254/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bn254/setup.go b/backend/groth16/bn254/setup.go
index b9fd6a0563..13ddcd61d3 100644
--- a/backend/groth16/bn254/setup.go
+++ b/backend/groth16/bn254/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bn254/solidity.go b/backend/groth16/bn254/solidity.go
index a8620efe75..47793bdf7e 100644
--- a/backend/groth16/bn254/solidity.go
+++ b/backend/groth16/bn254/solidity.go
@@ -98,16 +98,17 @@ contract Verifier {
{{- if gt $numCommitments 0 }}
// Pedersen G point in G2 in powers of i
- uint256 constant PEDERSEN_G_X_0 = {{ (fpstr .Vk.CommitmentKey.G.X.A0) }};
- uint256 constant PEDERSEN_G_X_1 = {{ (fpstr .Vk.CommitmentKey.G.X.A1) }};
- uint256 constant PEDERSEN_G_Y_0 = {{ (fpstr .Vk.CommitmentKey.G.Y.A0) }};
- uint256 constant PEDERSEN_G_Y_1 = {{ (fpstr .Vk.CommitmentKey.G.Y.A1) }};
-
- // Pedersen GRootSigmaNeg point in G2 in powers of i
- uint256 constant PEDERSEN_GROOTSIGMANEG_X_0 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.X.A0) }};
- uint256 constant PEDERSEN_GROOTSIGMANEG_X_1 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.X.A1) }};
- uint256 constant PEDERSEN_GROOTSIGMANEG_Y_0 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.Y.A0) }};
- uint256 constant PEDERSEN_GROOTSIGMANEG_Y_1 = {{ (fpstr .Vk.CommitmentKey.GRootSigmaNeg.Y.A1) }};
+ {{- $cmtVk0 := index .Vk.CommitmentKeys 0 }}
+ uint256 constant PEDERSEN_G_X_0 = {{ (fpstr $cmtVk0.G.X.A0) }};
+ uint256 constant PEDERSEN_G_X_1 = {{ (fpstr $cmtVk0.G.X.A1) }};
+ uint256 constant PEDERSEN_G_Y_0 = {{ (fpstr $cmtVk0.G.Y.A0) }};
+ uint256 constant PEDERSEN_G_Y_1 = {{ (fpstr $cmtVk0.G.Y.A1) }};
+
+ // Pedersen GSigma point in G2 in powers of i
+ uint256 constant PEDERSEN_GSIGMA_X_0 = {{ (fpstr $cmtVk0.GSigma.X.A0) }};
+ uint256 constant PEDERSEN_GSIGMA_X_1 = {{ (fpstr $cmtVk0.GSigma.X.A1) }};
+ uint256 constant PEDERSEN_GSIGMA_Y_0 = {{ (fpstr $cmtVk0.GSigma.Y.A0) }};
+ uint256 constant PEDERSEN_GSIGMA_Y_1 = {{ (fpstr $cmtVk0.GSigma.Y.A1) }};
{{- end }}
// Constant and public input points
@@ -566,7 +567,7 @@ contract Verifier {
{{- end }}
publicCommitments[{{$i}}] = uint256(
- sha256(
+ {{ hashFnName }}(
abi.encodePacked(
commitments[{{mul $i 2}}],
commitments[{{sum (mul $i 2) 1}}],
@@ -578,16 +579,16 @@ contract Verifier {
// Commitments
pairings[ 0] = commitments[0];
pairings[ 1] = commitments[1];
- pairings[ 2] = PEDERSEN_G_X_1;
- pairings[ 3] = PEDERSEN_G_X_0;
- pairings[ 4] = PEDERSEN_G_Y_1;
- pairings[ 5] = PEDERSEN_G_Y_0;
+ pairings[ 2] = PEDERSEN_GSIGMA_X_1;
+ pairings[ 3] = PEDERSEN_GSIGMA_X_0;
+ pairings[ 4] = PEDERSEN_GSIGMA_Y_1;
+ pairings[ 5] = PEDERSEN_GSIGMA_Y_0;
pairings[ 6] = Px;
pairings[ 7] = Py;
- pairings[ 8] = PEDERSEN_GROOTSIGMANEG_X_1;
- pairings[ 9] = PEDERSEN_GROOTSIGMANEG_X_0;
- pairings[10] = PEDERSEN_GROOTSIGMANEG_Y_1;
- pairings[11] = PEDERSEN_GROOTSIGMANEG_Y_0;
+ pairings[ 8] = PEDERSEN_G_X_1;
+ pairings[ 9] = PEDERSEN_G_X_0;
+ pairings[10] = PEDERSEN_G_Y_1;
+ pairings[11] = PEDERSEN_G_Y_0;
// Verify pedersen commitments
bool success;
@@ -713,7 +714,7 @@ contract Verifier {
{{- end }}
publicCommitments[{{$i}}] = uint256(
- sha256(
+ {{ hashFnName }}(
abi.encodePacked(
commitments[{{mul $i 2}}],
commitments[{{sum (mul $i 2) 1}}],
@@ -729,15 +730,15 @@ contract Verifier {
let f := mload(0x40)
calldatacopy(f, commitments, 0x40) // Copy Commitments
- mstore(add(f, 0x40), PEDERSEN_G_X_1)
- mstore(add(f, 0x60), PEDERSEN_G_X_0)
- mstore(add(f, 0x80), PEDERSEN_G_Y_1)
- mstore(add(f, 0xa0), PEDERSEN_G_Y_0)
+ mstore(add(f, 0x40), PEDERSEN_GSIGMA_X_1)
+ mstore(add(f, 0x60), PEDERSEN_GSIGMA_X_0)
+ mstore(add(f, 0x80), PEDERSEN_GSIGMA_Y_1)
+ mstore(add(f, 0xa0), PEDERSEN_GSIGMA_Y_0)
calldatacopy(add(f, 0xc0), commitmentPok, 0x40)
- mstore(add(f, 0x100), PEDERSEN_GROOTSIGMANEG_X_1)
- mstore(add(f, 0x120), PEDERSEN_GROOTSIGMANEG_X_0)
- mstore(add(f, 0x140), PEDERSEN_GROOTSIGMANEG_Y_1)
- mstore(add(f, 0x160), PEDERSEN_GROOTSIGMANEG_Y_0)
+ mstore(add(f, 0x100), PEDERSEN_G_X_1)
+ mstore(add(f, 0x120), PEDERSEN_G_X_0)
+ mstore(add(f, 0x140), PEDERSEN_G_Y_1)
+ mstore(add(f, 0x160), PEDERSEN_G_Y_0)
success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x180, f, 0x20)
success := and(success, mload(f))
diff --git a/backend/groth16/bn254/verify.go b/backend/groth16/bn254/verify.go
index b275d0109c..bc9f1bcc5a 100644
--- a/backend/groth16/bn254/verify.go
+++ b/backend/groth16/bn254/verify.go
@@ -17,9 +17,12 @@
package groth16
import (
+ "bytes"
+ "crypto/sha256"
"errors"
"fmt"
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
+ "golang.org/x/crypto/sha3"
"io"
"math/big"
"text/template"
@@ -101,14 +104,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
@@ -151,6 +152,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
//
// See https://github.com/ConsenSys/gnark-tests for example usage.
func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error {
+ cfg, err := solidity.NewExportConfig(exportOpts...)
+ log := logger.Logger()
+ if err != nil {
+ return err
+ }
+ if cfg.HashToFieldFn == nil {
+ // set the target hash function to legacy keccak256 as it is the default for `solidity.WithTargetSolidityVerifier``
+ cfg.HashToFieldFn = sha3.NewLegacyKeccak256()
+ log.Debug().Msg("hash to field function not set, using keccak256 as default")
+ }
+ // a bit hacky way to understand what hash function is provided. We already
+ // receive instance of hash function but it is difficult to compare it with
+ // sha256.New() or sha3.NewLegacyKeccak256() directly.
+ //
+ // So, we hash an empty input and compare the outputs.
+ cfg.HashToFieldFn.Reset()
+ hashBts := cfg.HashToFieldFn.Sum(nil)
+ var hashFnName string
+ if bytes.Equal(hashBts, sha256.New().Sum(nil)) {
+ hashFnName = "sha256"
+ } else if bytes.Equal(hashBts, sha3.NewLegacyKeccak256().Sum(nil)) {
+ hashFnName = "keccak256"
+ } else {
+ return fmt.Errorf("unsupported hash function used, only supported sha256 and legacy keccak256")
+ }
+ cfg.HashToFieldFn.Reset()
helpers := template.FuncMap{
"sum": func(a, b int) int {
return a + b
@@ -173,9 +200,11 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor
x.BigInt(bv)
return bv.String()
},
+ "hashFnName": func() string {
+ return hashFnName
+ },
}
- log := logger.Logger()
if len(vk.PublicAndCommitmentCommitted) > 1 {
log.Warn().Msg("exporting solidity verifier with more than one commitment is not supported")
} else if len(vk.PublicAndCommitmentCommitted) == 1 {
@@ -195,11 +224,6 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor
vk.G2.Gamma, vk.G2.gammaNeg = vk.G2.gammaNeg, vk.G2.Gamma
vk.G2.Delta, vk.G2.deltaNeg = vk.G2.deltaNeg, vk.G2.Delta
- cfg, err := solidity.NewExportConfig(exportOpts...)
- if err != nil {
- return err
- }
-
// execute template
err = tmpl.Execute(w, struct {
Cfg solidity.ExportConfig
diff --git a/backend/groth16/bw6-633/commitment_test.go b/backend/groth16/bw6-633/commitment_test.go
index a832752ffd..fa5eaf36b5 100644
--- a/backend/groth16/bw6-633/commitment_test.go
+++ b/backend/groth16/bw6-633/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bw6-633/marshal.go b/backend/groth16/bw6-633/marshal.go
index a801462c9f..d0d6ff3809 100644
--- a/backend/groth16/bw6-633/marshal.go
+++ b/backend/groth16/bw6-633/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bw6-633/marshal_test.go b/backend/groth16/bw6-633/marshal_test.go
index 2ff195dc2a..53fa6e8573 100644
--- a/backend/groth16/bw6-633/marshal_test.go
+++ b/backend/groth16/bw6-633/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bw6-633/prove.go b/backend/groth16/bw6-633/prove.go
index 55be59061e..a4a6153c45 100644
--- a/backend/groth16/bw6-633/prove.go
+++ b/backend/groth16/bw6-633/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bw6-633/fr"
"github.com/consensys/gnark-crypto/ecc/bw6-633/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bw6-633/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bw6-633/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bw6-633/setup.go b/backend/groth16/bw6-633/setup.go
index 466755b840..d26cd7c324 100644
--- a/backend/groth16/bw6-633/setup.go
+++ b/backend/groth16/bw6-633/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bw6-633/verify.go b/backend/groth16/bw6-633/verify.go
index fb4b6dbb24..79ce6b0295 100644
--- a/backend/groth16/bw6-633/verify.go
+++ b/backend/groth16/bw6-633/verify.go
@@ -98,14 +98,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
diff --git a/backend/groth16/bw6-761/commitment_test.go b/backend/groth16/bw6-761/commitment_test.go
index f16cb25786..323b82f097 100644
--- a/backend/groth16/bw6-761/commitment_test.go
+++ b/backend/groth16/bw6-761/commitment_test.go
@@ -148,3 +148,34 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
Two: 2,
})
}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
+}
diff --git a/backend/groth16/bw6-761/marshal.go b/backend/groth16/bw6-761/marshal.go
index b89105e280..a5766ae5b3 100644
--- a/backend/groth16/bw6-761/marshal.go
+++ b/backend/groth16/bw6-761/marshal.go
@@ -95,24 +95,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -126,40 +116,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -168,66 +162,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
}
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
+ }
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
+
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
- return dec.BytesRead(), nil
+ return n + dec.BytesRead(), nil
}
// WriteTo writes binary encoding of the key elements to writer
@@ -296,7 +296,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/backend/groth16/bw6-761/marshal_test.go b/backend/groth16/bw6-761/marshal_test.go
index 3be88f02a2..ab8fbc8667 100644
--- a/backend/groth16/bw6-761/marshal_test.go
+++ b/backend/groth16/bw6-761/marshal_test.go
@@ -98,6 +98,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -107,9 +108,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/backend/groth16/bw6-761/prove.go b/backend/groth16/bw6-761/prove.go
index f98967d4b6..53de1f477a 100644
--- a/backend/groth16/bw6-761/prove.go
+++ b/backend/groth16/bw6-761/prove.go
@@ -23,7 +23,6 @@ import (
"github.com/consensys/gnark-crypto/ecc/bw6-761/fr"
"github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft"
"github.com/consensys/gnark-crypto/ecc/bw6-761/fr/hash_to_field"
- "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/pedersen"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16/internal"
"github.com/consensys/gnark/backend/witness"
@@ -118,7 +117,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -127,7 +134,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/backend/groth16/bw6-761/setup.go b/backend/groth16/bw6-761/setup.go
index 8f7cfc1f4b..0988613d36 100644
--- a/backend/groth16/bw6-761/setup.go
+++ b/backend/groth16/bw6-761/setup.go
@@ -77,7 +77,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -287,10 +287,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/backend/groth16/bw6-761/verify.go b/backend/groth16/bw6-761/verify.go
index 718578e64f..a00e52d69c 100644
--- a/backend/groth16/bw6-761/verify.go
+++ b/backend/groth16/bw6-761/verify.go
@@ -98,14 +98,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
diff --git a/backend/plonk/bn254/verify.go b/backend/plonk/bn254/verify.go
index 2197228b3f..c42e1aeb66 100644
--- a/backend/plonk/bn254/verify.go
+++ b/backend/plonk/bn254/verify.go
@@ -434,6 +434,9 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor
if err != nil {
return err
}
+ if cfg.HashToFieldFn != nil {
+ return fmt.Errorf("setting hash to field function is not supported for PLONK Solidity export. Hash function is hardcoded to RFC9380")
+ }
return t.Execute(w, struct {
Cfg solidity.ExportConfig
diff --git a/backend/solidity/solidity.go b/backend/solidity/solidity.go
index e5a5a35005..1dbe5c4666 100644
--- a/backend/solidity/solidity.go
+++ b/backend/solidity/solidity.go
@@ -1,5 +1,13 @@
package solidity
+import (
+ "fmt"
+ "hash"
+
+ "github.com/consensys/gnark/backend"
+ "golang.org/x/crypto/sha3"
+)
+
// ExportOption defines option for altering the behavior of the prover in
// Prove, ReadAndProve and IsSolved methods. See the descriptions of functions
// returning instances of this type for implemented options.
@@ -8,13 +16,15 @@ type ExportOption func(*ExportConfig) error
// ExportConfig is the configuration for the prover with the options applied.
type ExportConfig struct {
PragmaVersion string
+ HashToFieldFn hash.Hash
}
// NewExportConfig returns a default ExportConfig with given export options opts
// applied.
func NewExportConfig(opts ...ExportOption) (ExportConfig, error) {
config := ExportConfig{
- PragmaVersion: "0.8.24",
+ // we set default pragma version to 0.8.0+ to avoid needing to sync Solidity CI all the time
+ PragmaVersion: "^0.8.0",
}
for _, option := range opts {
if err := option(&config); err != nil {
@@ -31,3 +41,67 @@ func WithPragmaVersion(version string) ExportOption {
return nil
}
}
+
+// WithHashToFieldFunction changes the hash function used for hashing
+// bytes to field. If not set then the default hash function based on RFC 9380
+// is used. Used mainly for compatibility between different systems and
+// efficient recursion.
+func WithHashToFieldFunction(hFunc hash.Hash) ExportOption {
+ return func(cfg *ExportConfig) error {
+ cfg.HashToFieldFn = hFunc
+ return nil
+ }
+}
+
+// WithProverTargetSolidityVerifier returns a prover option that sets all the
+// necessary prover options which are suitable for verifying the proofs in the
+// Solidity verifier.
+//
+// For PLONK this is a no-op option as the Solidity verifier is directly
+// compatible with the default prover options. Regardless, it is recommended to
+// use this option for consistency and possible future changes in the Solidity
+// verifier.
+//
+// For Groth16 this option sets the hash function used for hashing bytes to
+// field to [sha3.NewLegacyKeccak256] as the Solidity verifier does not support
+// the standard hash-to-field function. We use legacy Keccak256 in Solidity for
+// the cheapest gas usage.
+func WithProverTargetSolidityVerifier(bid backend.ID) backend.ProverOption {
+ switch bid {
+ case backend.GROTH16:
+ // Solidity verifier does not support standard hash-to-field function.
+ // Choose efficient one.
+ return backend.WithProverHashToFieldFunction(sha3.NewLegacyKeccak256())
+ case backend.PLONK:
+ // default hash function works for PLONK. We just have to return a no-op option
+ return func(*backend.ProverConfig) error {
+ return nil
+ }
+ default:
+ return func(*backend.ProverConfig) error {
+ return fmt.Errorf("unsupported backend ID: %s", bid)
+ }
+ }
+}
+
+// WithVerifierTargetSolidityVerifier returns a verifier option that sets all
+// the necessary verifier options which are suitable for verifying the proofs
+// targeted for the Solidity verifier. See the comments in
+// [WithProverTargetSolidityVerifier].
+func WithVerifierTargetSolidityVerifier(bid backend.ID) backend.VerifierOption {
+ switch bid {
+ case backend.GROTH16:
+ // Solidity verifier does not support standard hash-to-field function.
+ // Choose efficient one.
+ return backend.WithVerifierHashToFieldFunction(sha3.NewLegacyKeccak256())
+ case backend.PLONK:
+ // default hash function works for PLONK. We just have to return a no-op option
+ return func(*backend.VerifierConfig) error {
+ return nil
+ }
+ default:
+ return func(*backend.VerifierConfig) error {
+ return fmt.Errorf("unsupported backend ID: %s", bid)
+ }
+ }
+}
diff --git a/backend/solidity/solidity_test.go b/backend/solidity/solidity_test.go
new file mode 100644
index 0000000000..aeb2db1ea3
--- /dev/null
+++ b/backend/solidity/solidity_test.go
@@ -0,0 +1,176 @@
+package solidity_test
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "hash"
+ "testing"
+
+ "github.com/consensys/gnark-crypto/ecc"
+ "github.com/consensys/gnark/backend"
+ "github.com/consensys/gnark/backend/solidity"
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gnark/test"
+ "golang.org/x/crypto/sha3"
+)
+
+type noCommitCircuit struct {
+ A, B, Out frontend.Variable `gnark:",public"`
+}
+
+func (c *noCommitCircuit) Define(api frontend.API) error {
+ res := api.Mul(c.A, c.B)
+ api.AssertIsEqual(res, c.Out)
+ return nil
+}
+
+type commitCircuit struct {
+ A, B, Out frontend.Variable `gnark:",public"`
+}
+
+func (c *commitCircuit) Define(api frontend.API) error {
+ res := api.Mul(c.A, c.B)
+ api.AssertIsEqual(res, c.Out)
+ cmter, ok := api.(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("api does not support commitment")
+ }
+ cmt1, err := cmter.Commit(res)
+ if err != nil {
+ return err
+ }
+ api.AssertIsDifferent(cmt1, res)
+ return nil
+}
+
+type twoCommitCircuit struct {
+ A, B, Out frontend.Variable `gnark:",public"`
+}
+
+func (c *twoCommitCircuit) Define(api frontend.API) error {
+ res := api.Mul(c.A, c.B)
+ api.AssertIsEqual(res, c.Out)
+ cmter, ok := api.(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("api does not support commitment")
+ }
+ cmt1, err := cmter.Commit(res)
+ if err != nil {
+ return err
+ }
+ cmt2, err := cmter.Commit(cmt1)
+ if err != nil {
+ return err
+ }
+ api.AssertIsDifferent(cmt1, cmt2)
+ return nil
+}
+
+func TestNoCommitment(t *testing.T) {
+ // should succeed both with G16 and PLONK:
+ assert := test.NewAssert(t)
+ circuit := &noCommitCircuit{}
+ assignment := &noCommitCircuit{A: 2, B: 3, Out: 6}
+ defaultOpts := []test.TestingOption{
+ test.WithCurves(ecc.BN254),
+ test.WithValidAssignment(assignment),
+ }
+ checkCircuit := func(assert *test.Assert, bid backend.ID) {
+ opts := append(defaultOpts,
+ test.WithBackends(bid),
+ )
+
+ assert.CheckCircuit(circuit, opts...)
+ }
+ assert.Run(func(assert *test.Assert) {
+ checkCircuit(assert, backend.GROTH16)
+ }, "Groth16")
+ assert.Run(func(assert *test.Assert) {
+ checkCircuit(assert, backend.PLONK)
+ }, "PLONK")
+}
+
+func TestSingleCommitment(t *testing.T) {
+ // should succeed both with G16 and PLONK:
+ // - But for G16 only if the hash-to-field is set to a supported one.
+ // - but for PLONK only if the hash-to-field is the default one. If not, then it should fail.
+ assert := test.NewAssert(t)
+ circuit := &commitCircuit{}
+ assignment := &commitCircuit{A: 2, B: 3, Out: 6}
+ defaultOpts := []test.TestingOption{
+ test.WithCurves(ecc.BN254),
+ test.WithValidAssignment(assignment),
+ }
+ checkCircuit := func(assert *test.Assert, bid backend.ID, newHash func() hash.Hash) {
+ opts := append(defaultOpts,
+ test.WithBackends(bid),
+ test.WithProverOpts(
+ backend.WithProverHashToFieldFunction(newHash()),
+ ),
+ test.WithVerifierOpts(
+ backend.WithVerifierHashToFieldFunction(newHash()),
+ ),
+ test.WithSolidityExportOptions(solidity.WithHashToFieldFunction(newHash())),
+ )
+
+ assert.CheckCircuit(circuit, opts...)
+ }
+ // G16 success with explicitly set options
+ assert.Run(func(assert *test.Assert) {
+ checkCircuit(assert, backend.GROTH16, sha256.New)
+ }, "groth16", "sha256")
+ assert.Run(func(assert *test.Assert) {
+ checkCircuit(assert, backend.GROTH16, sha3.NewLegacyKeccak256)
+ }, "groth16", "keccak256")
+ // G16 success with using TargetSolidityVerifier
+ assert.Run(func(assert *test.Assert) {
+ opts := append(defaultOpts,
+ test.WithBackends(backend.GROTH16),
+ test.WithProverOpts(
+ solidity.WithProverTargetSolidityVerifier(backend.GROTH16),
+ ),
+ test.WithVerifierOpts(
+ solidity.WithVerifierTargetSolidityVerifier(backend.GROTH16),
+ ),
+ )
+ assert.CheckCircuit(circuit, opts...)
+ }, "groth16", "targetSolidityVerifier")
+ // G16 success without any options because we set default options already in
+ // assert.CheckCircuit if they are not set.
+ assert.Run(func(assert *test.Assert) {
+ opts := append(defaultOpts,
+ test.WithBackends(backend.GROTH16),
+ )
+ assert.CheckCircuit(circuit, opts...)
+ }, "groth16", "no-options")
+
+ // PLONK success with default options
+ assert.Run(func(assert *test.Assert) {
+ opts := append(defaultOpts,
+ test.WithBackends(backend.PLONK),
+ )
+ assert.CheckCircuit(circuit, opts...)
+ }, "plonk", "default")
+ // PLONK success with using TargetSolidityVerifier
+ assert.Run(func(assert *test.Assert) {
+ opts := append(defaultOpts,
+ test.WithBackends(backend.PLONK),
+ test.WithProverOpts(
+ solidity.WithProverTargetSolidityVerifier(backend.PLONK),
+ ),
+ test.WithVerifierOpts(
+ solidity.WithVerifierTargetSolidityVerifier(backend.PLONK),
+ ),
+ )
+ assert.CheckCircuit(circuit, opts...)
+ }, "plonk", "targetSolidityVerifier")
+}
+
+func TestTwoCommitments(t *testing.T) {
+ // should succeed with PLONK only.
+ // - but for PLONK only if the hash-to-field is the default one. If not, then it should fail.
+ assert := test.NewAssert(t)
+ circuit := &twoCommitCircuit{}
+ assignment := &twoCommitCircuit{A: 2, B: 3, Out: 6}
+ assert.CheckCircuit(circuit, test.WithCurves(ecc.BN254), test.WithValidAssignment(assignment), test.WithBackends(backend.PLONK))
+}
diff --git a/doc.go b/doc.go
index 73f850a3df..e067f90998 100644
--- a/doc.go
+++ b/doc.go
@@ -22,7 +22,7 @@ import (
"github.com/consensys/gnark-crypto/ecc"
)
-var Version = semver.MustParse("0.10.0")
+var Version = semver.MustParse("0.11.0")
// Curves return the curves supported by gnark
func Curves() []ecc.ID {
diff --git a/docs/CITATION.bib b/docs/CITATION.bib
index 51afca5ac2..1ed73fa2d1 100644
--- a/docs/CITATION.bib
+++ b/docs/CITATION.bib
@@ -1,14 +1,14 @@
-@software{gnark-v0.10.0,
+@software{gnark-v0.11.0,
author = {Gautam Botrel and
Thomas Piellard and
Youssef El Housni and
Ivo Kubjas and
Arya Tabaie},
- title = {ConsenSys/gnark: v0.10.0},
- month = apr,
+ title = {ConsenSys/gnark: v0.11.0},
+ month = sep,
year = 2024,
publisher = {Zenodo},
- version = {v0.10.0},
+ version = {v0.11.0},
doi = {10.5281/zenodo.5819104},
url = {https://doi.org/10.5281/zenodo.5819104}
}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 34a2100082..4ed242d670 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/consensys/bavard v0.1.13
github.com/consensys/compress v0.2.5
- github.com/consensys/gnark-crypto v0.13.1-0.20240802214859-ff4c0ddbe1ef
+ github.com/consensys/gnark-crypto v0.14.0
github.com/fxamacker/cbor/v2 v2.7.0
github.com/google/go-cmp v0.6.0
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8
diff --git a/go.sum b/go.sum
index 72842c60a3..efc71ddf96 100644
--- a/go.sum
+++ b/go.sum
@@ -61,8 +61,8 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk=
github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk=
-github.com/consensys/gnark-crypto v0.13.1-0.20240802214859-ff4c0ddbe1ef h1:4DaS1IYXk0vKcCdguGjkHVyN43YqmKUmpYDxb90VBnU=
-github.com/consensys/gnark-crypto v0.13.1-0.20240802214859-ff4c0ddbe1ef/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o=
+github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E=
+github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
diff --git a/internal/generator/backend/template/zkpschemes/groth16/groth16.marshal.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/groth16.marshal.go.tmpl
index 4199f6cf47..5f11ac043b 100644
--- a/internal/generator/backend/template/zkpschemes/groth16/groth16.marshal.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/groth16/groth16.marshal.go.tmpl
@@ -77,24 +77,14 @@ func (proof *Proof) ReadFrom(r io.Reader) (n int64, err error) {
// points are compressed
// use WriteRawTo(...) to encode the key without point compression
func (vk *VerifyingKey) WriteTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, false); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteTo(w)
- return m + n, err
+ return vk.writeTo(w, false)
}
// WriteRawTo writes binary encoding of the key elements to writer
// points are not compressed
// use WriteTo(...) to encode the key with point compression
func (vk *VerifyingKey) WriteRawTo(w io.Writer) (n int64, err error) {
- if n, err = vk.writeTo(w, true); err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.WriteRawTo(w)
- return m + n, err
+ return vk.writeTo(w, true)
}
// writeTo serialization format:
@@ -108,41 +98,44 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
} else {
enc = curve.NewEncoder(w)
}
-
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := enc.Encode(&vk.G1.Alpha); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Beta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Gamma); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G1.Delta); err != nil {
- return enc.BytesWritten(), err
- }
- if err := enc.Encode(&vk.G2.Delta); err != nil {
- return enc.BytesWritten(), err
- }
-
- // uint32(len(Kvk)),[Kvk]1
- if err := enc.Encode(vk.G1.K); err != nil {
- return enc.BytesWritten(), err
- }
-
if vk.PublicAndCommitmentCommitted == nil {
vk.PublicAndCommitmentCommitted = [][]int{} // only matters in tests
}
- if err := enc.Encode(utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted)); err != nil {
- return enc.BytesWritten(), err
+ toEncode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ vk.G1.K,
+ utils.IntSliceSliceToUint64SliceSlice(vk.PublicAndCommitmentCommitted),
+ uint32(len(vk.CommitmentKeys)),
}
-
- return enc.BytesWritten(), nil
+ for _, v := range toEncode {
+ if err := enc.Encode(v); err != nil {
+ return enc.BytesWritten(), err
+ }
+ }
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].WriteRawTo(w)
+ } else {
+ m, err = vk.CommitmentKeys[i].WriteTo(w)
+ }
+ n += m
+ if err != nil {
+ return n + enc.BytesWritten(), err
+ }
+ }
+ return n + enc.BytesWritten(), nil
}
// ReadFrom attempts to decode a VerifyingKey from reader
@@ -151,66 +144,72 @@ func (vk *VerifyingKey) writeTo(w io.Writer, raw bool) (int64, error) {
// https://github.com/zkcrypto/bellman/blob/fa9be45588227a8c6ec34957de3f68705f07bd92/src/groth16/mod.rs#L143
// [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2,uint32(len(Kvk)),[Kvk]1
func (vk *VerifyingKey) ReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r)
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.ReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, false)
}
// UnsafeReadFrom has the same behavior as ReadFrom, except that it will not check that decode points
// are on the curve and in the correct subgroup.
func (vk *VerifyingKey) UnsafeReadFrom(r io.Reader) (int64, error) {
- n, err := vk.readFrom(r, curve.NoSubgroupChecks())
- if err != nil {
- return n, err
- }
- var m int64
- m, err = vk.CommitmentKey.UnsafeReadFrom(r)
- return m + n, err
+ return vk.readFrom(r, true)
}
-func (vk *VerifyingKey) readFrom(r io.Reader, decOptions ...func(*curve.Decoder)) (int64, error) {
- dec := curve.NewDecoder(r, decOptions...)
-
- // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
- if err := dec.Decode(&vk.G1.Alpha); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Beta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Gamma); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G1.Delta); err != nil {
- return dec.BytesRead(), err
- }
- if err := dec.Decode(&vk.G2.Delta); err != nil {
- return dec.BytesRead(), err
+func (vk *VerifyingKey) readFrom(r io.Reader, raw bool) (int64, error) {
+ var dec *curve.Decoder
+ if raw {
+ dec = curve.NewDecoder(r, curve.NoSubgroupChecks())
+ } else {
+ dec = curve.NewDecoder(r)
}
- // uint32(len(Kvk)),[Kvk]1
- if err := dec.Decode(&vk.G1.K); err != nil {
- return dec.BytesRead(), err
- }
var publicCommitted [][]uint64
- if err := dec.Decode(&publicCommitted); err != nil {
- return dec.BytesRead(), err
+ var nbCommitments uint32
+
+ toDecode := []interface{}{
+ // [α]1,[β]1,[β]2,[γ]2,[δ]1,[δ]2
+ &vk.G1.Alpha,
+ &vk.G1.Beta,
+ &vk.G2.Beta,
+ &vk.G2.Gamma,
+ &vk.G1.Delta,
+ &vk.G2.Delta,
+ // uint32(len(Kvk)),[Kvk]1
+ &vk.G1.K,
+ &publicCommitted,
+ &nbCommitments,
+ }
+
+ for _, v := range toDecode {
+ if err := dec.Decode(v); err != nil {
+ return dec.BytesRead(), err
+ }
}
+
vk.PublicAndCommitmentCommitted = utils.Uint64SliceSliceToIntSliceSlice(publicCommitted)
+
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, nbCommitments)
+ var n int64
+ for i := range vk.CommitmentKeys {
+ var (
+ m int64
+ err error
+ )
+ if raw {
+ m, err = vk.CommitmentKeys[i].UnsafeReadFrom(r)
+ } else {
+ m, err = vk.CommitmentKeys[i].ReadFrom(r)
+ }
+ n += m
+ if err != nil {
+ return n + dec.BytesRead(), err
+ }
+ }
// recompute vk.e (e(α, β)) and -[δ]2, -[γ]2
if err := vk.Precompute(); err != nil {
- return dec.BytesRead(), err
+ return n + dec.BytesRead(), err
}
-
- return dec.BytesRead(), nil
+
+ return n + dec.BytesRead(), nil
}
@@ -282,7 +281,7 @@ func (pk *ProvingKey) writeTo(w io.Writer, raw bool) (int64, error) {
n += n2
if err != nil {
- return n, err
+ return n + enc.BytesWritten(), err
}
}
diff --git a/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl
index 4bc05dd14d..b94532519c 100644
--- a/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl
@@ -9,7 +9,6 @@ import (
{{- template "import_backend_cs" . }}
{{- template "import_fft" . }}
{{- template "import_hash_to_field" . }}
- {{- template "import_pedersen" .}}
"github.com/consensys/gnark/constraint"
"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/internal/utils"
@@ -102,7 +101,15 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
wireValues := []fr.Element(solution.W)
start := time.Now()
+ poks := make([]curve.G1Affine, len(pk.CommitmentKeys))
+ for i := range pk.CommitmentKeys {
+ var err error
+ if poks[i], err = pk.CommitmentKeys[i].ProveKnowledge(privateCommittedValues[i]); err != nil {
+ return nil, err
+ }
+ }
+ // compute challenge for folding the PoKs from the commitments
commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo))
for i := range commitmentInfo {
copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal())
@@ -111,7 +118,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b
if err != nil {
return nil, err
}
- if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, challenge[0]); err != nil {
+ if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil {
return nil, err
}
diff --git a/internal/generator/backend/template/zkpschemes/groth16/groth16.setup.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/groth16.setup.go.tmpl
index 81af9215b5..ae86a7c39e 100644
--- a/internal/generator/backend/template/zkpschemes/groth16/groth16.setup.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/groth16/groth16.setup.go.tmpl
@@ -59,7 +59,7 @@ type VerifyingKey struct {
// e(α, β)
e curve.GT // not serialized
- CommitmentKey pedersen.VerifyingKey
+ CommitmentKeys []pedersen.VerifyingKey
PublicAndCommitmentCommitted [][]int // indexes of public/commitment committed variables
}
@@ -269,10 +269,20 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
return errors.New("didn't consume all G1 points") // TODO @Tabaie Remove this
}
- pk.CommitmentKeys, vk.CommitmentKey, err = pedersen.Setup(commitmentBases)
+ cG2, err := curve.RandomOnG2()
if err != nil {
return err
}
+ pk.CommitmentKeys = make([]pedersen.ProvingKey, len(commitmentBases))
+ vk.CommitmentKeys = make([]pedersen.VerifyingKey, len(commitmentBases))
+ for i := range commitmentBases {
+ comPKey, comVKey, err := pedersen.Setup(commitmentBases[i:i+1], pedersen.WithG2Point(cG2))
+ if err != nil {
+ return err
+ }
+ pk.CommitmentKeys[i] = comPKey[0]
+ vk.CommitmentKeys[i] = comVKey
+ }
vk.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentWires, r1cs.GetNbPublicVariables())
diff --git a/internal/generator/backend/template/zkpschemes/groth16/groth16.verify.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/groth16.verify.go.tmpl
index e1ec6e6c02..97e953a7d6 100644
--- a/internal/generator/backend/template/zkpschemes/groth16/groth16.verify.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/groth16/groth16.verify.go.tmpl
@@ -5,6 +5,9 @@ import (
{{- if eq .Curve "BN254"}}
"math/big"
"text/template"
+ "bytes"
+ "crypto/sha256"
+ "golang.org/x/crypto/sha3"
{{- template "import_fp" . }}
{{- end}}
"time"
@@ -85,14 +88,12 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
publicWitness = append(publicWitness, res)
copy(commitmentsSerialized[i*fr.Bytes:], res.Marshal())
}
- challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
- if err != nil {
- return err
- }
- if folded, err := pedersen.FoldCommitments(proof.Commitments, challenge[0]); err != nil {
- return err
- } else {
- if err = vk.CommitmentKey.Verify(folded, proof.CommitmentPok); err != nil {
+ if len(vk.CommitmentKeys) > 0 {
+ challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1)
+ if err != nil {
+ return err
+ }
+ if err = pedersen.BatchVerifyMultiVk(vk.CommitmentKeys, proof.Commitments, []curve.G1Affine{proof.CommitmentPok}, challenge[0]); err != nil {
return err
}
}
@@ -137,6 +138,32 @@ func Verify(proof *Proof, vk *VerifyingKey, publicWitness fr.Vector, opts ...bac
//
// See https://github.com/ConsenSys/gnark-tests for example usage.
func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error {
+ cfg, err := solidity.NewExportConfig(exportOpts...)
+ log := logger.Logger()
+ if err != nil {
+ return err
+ }
+ if cfg.HashToFieldFn == nil {
+ // set the target hash function to legacy keccak256 as it is the default for `solidity.WithTargetSolidityVerifier``
+ cfg.HashToFieldFn = sha3.NewLegacyKeccak256()
+ log.Debug().Msg("hash to field function not set, using keccak256 as default")
+ }
+ // a bit hacky way to understand what hash function is provided. We already
+ // receive instance of hash function but it is difficult to compare it with
+ // sha256.New() or sha3.NewLegacyKeccak256() directly.
+ //
+ // So, we hash an empty input and compare the outputs.
+ cfg.HashToFieldFn.Reset()
+ hashBts := cfg.HashToFieldFn.Sum(nil)
+ var hashFnName string
+ if bytes.Equal(hashBts, sha256.New().Sum(nil)) {
+ hashFnName = "sha256"
+ } else if bytes.Equal(hashBts, sha3.NewLegacyKeccak256().Sum(nil)) {
+ hashFnName = "keccak256"
+ } else {
+ return fmt.Errorf("unsupported hash function used, only supported sha256 and legacy keccak256")
+ }
+ cfg.HashToFieldFn.Reset()
helpers := template.FuncMap{
"sum": func(a, b int) int {
return a + b
@@ -159,9 +186,11 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor
x.BigInt(bv)
return bv.String()
},
+ "hashFnName": func() string {
+ return hashFnName
+ },
}
- log := logger.Logger()
if len(vk.PublicAndCommitmentCommitted) > 1 {
log.Warn().Msg("exporting solidity verifier with more than one commitment is not supported")
} else if len(vk.PublicAndCommitmentCommitted) == 1 {
@@ -181,15 +210,11 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor
vk.G2.Gamma, vk.G2.gammaNeg = vk.G2.gammaNeg, vk.G2.Gamma
vk.G2.Delta, vk.G2.deltaNeg = vk.G2.deltaNeg, vk.G2.Delta
- cfg, err := solidity.NewExportConfig(exportOpts...)
- if err != nil {
- return err
- }
// execute template
err = tmpl.Execute(w, struct {
- Cfg solidity.ExportConfig
- Vk VerifyingKey
+ Cfg solidity.ExportConfig
+ Vk VerifyingKey
}{
Cfg: cfg,
Vk: *vk,
diff --git a/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.commitment.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.commitment.go.tmpl
index cc94c21ef1..556754e3ea 100644
--- a/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.commitment.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.commitment.go.tmpl
@@ -129,4 +129,35 @@ func TestOneSecretOnePublicCommitted(t *testing.T) {
One: 1,
Two: 2,
})
+}
+
+type twoSecretCommitmentsCircuit struct {
+ One frontend.Variable
+ Two frontend.Variable
+}
+
+func (c *twoSecretCommitmentsCircuit) Define(api frontend.API) error {
+ commitCompiler, ok := api.Compiler().(frontend.Committer)
+ if !ok {
+ return fmt.Errorf("compiler does not commit")
+ }
+ commit1, err := commitCompiler.Commit(c.One)
+ if err != nil {
+ return err
+ }
+ commit2, err := commitCompiler.Commit(c.Two)
+ if err != nil {
+ return err
+ }
+ // constrain vars
+ api.AssertIsDifferent(commit1, 0)
+ api.AssertIsDifferent(commit2, 0)
+ return nil
+}
+
+func TestTwoSecretCommitments(t *testing.T) {
+ test(t, &twoSecretCommitmentsCircuit{}, &twoSecretCommitmentsCircuit{
+ One: 1,
+ Two: 2,
+ })
}
\ No newline at end of file
diff --git a/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.marshal.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.marshal.go.tmpl
index 7df6fa02b9..6997043589 100644
--- a/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.marshal.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/groth16/tests/groth16.marshal.go.tmpl
@@ -82,6 +82,7 @@ func TestVerifyingKeySerialization(t *testing.T) {
vk.G1.K[i] = p1
}
+ vk.CommitmentKeys = []pedersen.VerifyingKey{}
if withCommitment {
vk.PublicAndCommitmentCommitted = test_utils.Random2DIntSlice(5, 10) // TODO: Use gopter randomization
bases := make([][]curve.G1Affine, len(vk.PublicAndCommitmentCommitted))
@@ -91,9 +92,9 @@ func TestVerifyingKeySerialization(t *testing.T) {
for j := range bases[i] {
bases[i][j] = elem
elem.Add(&elem, &p1)
+ vk.CommitmentKeys = append(vk.CommitmentKeys, pedersen.VerifyingKey{G: p2, GSigma: p2})
}
}
- _, vk.CommitmentKey, err = pedersen.Setup(bases)
assert.NoError(t, err)
}
diff --git a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl
index 7ba4a42a48..f7368f4726 100644
--- a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl
+++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl
@@ -419,6 +419,9 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor
if err != nil {
return err
}
+ if cfg.HashToFieldFn != nil {
+ return fmt.Errorf("setting hash to field function is not supported for PLONK Solidity export. Hash function is hardcoded to RFC9380")
+ }
return t.Execute(w, struct {
Cfg solidity.ExportConfig
diff --git a/std/commitments/pedersen/assignment.go b/std/commitments/pedersen/assignment.go
index bb90d89f5c..8822c58187 100644
--- a/std/commitments/pedersen/assignment.go
+++ b/std/commitments/pedersen/assignment.go
@@ -34,35 +34,35 @@ func ValueOfVerifyingKey[G2El algebra.G2ElementT](vk any) (VerifyingKey[G2El], e
return ret, fmt.Errorf("expected *ped_bls12377.VerifyingKey, got %T", vk)
}
s.G = sw_bls12377.NewG2Affine(tVk.G)
- s.GRootSigmaNeg = sw_bls12377.NewG2Affine(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bls12377.NewG2Affine(tVk.GSigma)
case *VerifyingKey[sw_bls12381.G2Affine]:
tVk, ok := vk.(*ped_bls12381.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bls12381.VerifyingKey, got %T", vk)
}
s.G = sw_bls12381.NewG2Affine(tVk.G)
- s.GRootSigmaNeg = sw_bls12381.NewG2Affine(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bls12381.NewG2Affine(tVk.GSigma)
case *VerifyingKey[sw_bls24315.G2Affine]:
tVk, ok := vk.(*ped_bls24315.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bls24315.VerifyingKey, got %T", vk)
}
s.G = sw_bls24315.NewG2Affine(tVk.G)
- s.GRootSigmaNeg = sw_bls24315.NewG2Affine(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bls24315.NewG2Affine(tVk.GSigma)
case *VerifyingKey[sw_bw6761.G2Affine]:
tVk, ok := vk.(*ped_bw6761.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bw6761.VerifyingKey, got %T", vk)
}
s.G = sw_bw6761.NewG2Affine(tVk.G)
- s.GRootSigmaNeg = sw_bw6761.NewG2Affine(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bw6761.NewG2Affine(tVk.GSigma)
case *VerifyingKey[sw_bn254.G2Affine]:
tVk, ok := vk.(*ped_bn254.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bn254.VerifyingKey, got %T", vk)
}
s.G = sw_bn254.NewG2Affine(tVk.G)
- s.GRootSigmaNeg = sw_bn254.NewG2Affine(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bn254.NewG2Affine(tVk.GSigma)
default:
panic(fmt.Sprintf("unknown parametric type: %T", s))
}
@@ -82,35 +82,35 @@ func ValueOfVerifyingKeyFixed[G2El algebra.G2ElementT](vk any) (VerifyingKey[G2E
return ret, fmt.Errorf("expected *ped_bls12377.VerifyingKey, got %T", vk)
}
s.G = sw_bls12377.NewG2AffineFixed(tVk.G)
- s.GRootSigmaNeg = sw_bls12377.NewG2AffineFixed(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bls12377.NewG2AffineFixed(tVk.GSigma)
case *VerifyingKey[sw_bls12381.G2Affine]:
tVk, ok := vk.(*ped_bls12381.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bls12381.VerifyingKey, got %T", vk)
}
s.G = sw_bls12381.NewG2AffineFixed(tVk.G)
- s.GRootSigmaNeg = sw_bls12381.NewG2AffineFixed(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bls12381.NewG2AffineFixed(tVk.GSigma)
case *VerifyingKey[sw_bls24315.G2Affine]:
tVk, ok := vk.(*ped_bls24315.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bls24315.VerifyingKey, got %T", vk)
}
s.G = sw_bls24315.NewG2AffineFixed(tVk.G)
- s.GRootSigmaNeg = sw_bls24315.NewG2AffineFixed(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bls24315.NewG2AffineFixed(tVk.GSigma)
case *VerifyingKey[sw_bw6761.G2Affine]:
tVk, ok := vk.(*ped_bw6761.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bw6761.VerifyingKey, got %T", vk)
}
s.G = sw_bw6761.NewG2AffineFixed(tVk.G)
- s.GRootSigmaNeg = sw_bw6761.NewG2AffineFixed(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bw6761.NewG2AffineFixed(tVk.GSigma)
case *VerifyingKey[sw_bn254.G2Affine]:
tVk, ok := vk.(*ped_bn254.VerifyingKey)
if !ok {
return ret, fmt.Errorf("expected *ped_bn254.VerifyingKey, got %T", vk)
}
s.G = sw_bn254.NewG2AffineFixed(tVk.G)
- s.GRootSigmaNeg = sw_bn254.NewG2AffineFixed(tVk.GRootSigmaNeg)
+ s.GSigma = sw_bn254.NewG2AffineFixed(tVk.GSigma)
default:
return ret, fmt.Errorf("unknown parametric type: %T", s)
}
diff --git a/std/commitments/pedersen/verifier.go b/std/commitments/pedersen/verifier.go
index 136ad0a6bc..b1a9afed05 100644
--- a/std/commitments/pedersen/verifier.go
+++ b/std/commitments/pedersen/verifier.go
@@ -21,8 +21,8 @@ type KnowledgeProof[G1El algebra.G1ElementT] struct {
// VerifyingKey is a verifying key for Pedersen vector commitments.
type VerifyingKey[G2El algebra.G2ElementT] struct {
- G G2El
- GRootSigmaNeg G2El // (-1/σ)[G] for toxic σ
+ G G2El
+ GSigma G2El // (-1/σ)[G] for toxic σ
}
// Verifier verifies the knowledge proofs for a Pedersen commitments
@@ -63,7 +63,9 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertCommitment(commitment Commitment[
v.pairing.AssertIsOnG1(&knowledgeProof.G1El)
}
- v.pairing.PairingCheck([]*G1El{&commitment.G1El, &knowledgeProof.G1El}, []*G2El{&vk.G, &vk.GRootSigmaNeg})
+ if err = v.pairing.PairingCheck([]*G1El{&commitment.G1El, &knowledgeProof.G1El}, []*G2El{&vk.GSigma, &vk.G}); err != nil {
+ return fmt.Errorf("pairing check failed: %w", err)
+ }
return nil
}
diff --git a/std/recursion/groth16/verifier.go b/std/recursion/groth16/verifier.go
index dfa452174b..ad760db917 100644
--- a/std/recursion/groth16/verifier.go
+++ b/std/recursion/groth16/verifier.go
@@ -166,7 +166,7 @@ type VerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra
E GtEl
G1 struct{ K []G1El }
G2 struct{ GammaNeg, DeltaNeg G2El }
- CommitmentKey pedersen.VerifyingKey[G2El]
+ CommitmentKeys []pedersen.VerifyingKey[G2El]
PublicAndCommitmentCommitted [][]int
}
@@ -183,6 +183,7 @@ func PlaceholderVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, G
K: make([]G1El, ccs.GetNbPublicVariables()+len(ccs.GetCommitments().(constraint.Groth16Commitments))),
},
PublicAndCommitmentCommitted: commitments.GetPublicAndCommitmentCommitted(commitmentWires, ccs.GetNbPublicVariables()),
+ CommitmentKeys: make([]pedersen.VerifyingKey[G2El], len(commitments)),
}
}
@@ -212,9 +213,12 @@ func ValueOfVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bn254.NewG2Affine(deltaNeg)
s.G2.GammaNeg = sw_bn254.NewG2Affine(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKey[sw_bn254.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bn254.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKey[sw_bn254.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]:
tVk, ok := vk.(*groth16backend_bls12377.VerifyingKey)
@@ -236,9 +240,12 @@ func ValueOfVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bls12377.NewG2Affine(deltaNeg)
s.G2.GammaNeg = sw_bls12377.NewG2Affine(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKey[sw_bls12377.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bls12377.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKey[sw_bls12377.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bls12381.G1Affine, sw_bls12381.G2Affine, sw_bls12381.GTEl]:
tVk, ok := vk.(*groth16backend_bls12381.VerifyingKey)
@@ -260,9 +267,12 @@ func ValueOfVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bls12381.NewG2Affine(deltaNeg)
s.G2.GammaNeg = sw_bls12381.NewG2Affine(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKey[sw_bls12381.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bls12381.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKey[sw_bls12381.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bls24315.G1Affine, sw_bls24315.G2Affine, sw_bls24315.GT]:
tVk, ok := vk.(*groth16backend_bls24315.VerifyingKey)
@@ -284,9 +294,12 @@ func ValueOfVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bls24315.NewG2Affine(deltaNeg)
s.G2.GammaNeg = sw_bls24315.NewG2Affine(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKey[sw_bls24315.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bls24315.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKey[sw_bls24315.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]:
tVk, ok := vk.(*groth16backend_bw6761.VerifyingKey)
@@ -308,9 +321,12 @@ func ValueOfVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bw6761.NewG2Affine(deltaNeg)
s.G2.GammaNeg = sw_bw6761.NewG2Affine(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKey[sw_bw6761.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bw6761.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKey[sw_bw6761.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
default:
return ret, fmt.Errorf("unknown parametric type combination")
@@ -344,9 +360,12 @@ func ValueOfVerifyingKeyFixed[G1El algebra.G1ElementT, G2El algebra.G2ElementT,
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bn254.NewG2AffineFixed(deltaNeg)
s.G2.GammaNeg = sw_bn254.NewG2AffineFixed(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKeyFixed[sw_bn254.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bn254.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKeyFixed[sw_bn254.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]:
tVk, ok := vk.(*groth16backend_bls12377.VerifyingKey)
@@ -368,9 +387,12 @@ func ValueOfVerifyingKeyFixed[G1El algebra.G1ElementT, G2El algebra.G2ElementT,
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bls12377.NewG2AffineFixed(deltaNeg)
s.G2.GammaNeg = sw_bls12377.NewG2AffineFixed(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKeyFixed[sw_bls12377.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bls12377.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKeyFixed[sw_bls12377.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bls12381.G1Affine, sw_bls12381.G2Affine, sw_bls12381.GTEl]:
tVk, ok := vk.(*groth16backend_bls12381.VerifyingKey)
@@ -392,9 +414,12 @@ func ValueOfVerifyingKeyFixed[G1El algebra.G1ElementT, G2El algebra.G2ElementT,
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bls12381.NewG2AffineFixed(deltaNeg)
s.G2.GammaNeg = sw_bls12381.NewG2AffineFixed(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKeyFixed[sw_bls12381.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bls12381.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKeyFixed[sw_bls12381.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bls24315.G1Affine, sw_bls24315.G2Affine, sw_bls24315.GT]:
tVk, ok := vk.(*groth16backend_bls24315.VerifyingKey)
@@ -416,9 +441,12 @@ func ValueOfVerifyingKeyFixed[G1El algebra.G1ElementT, G2El algebra.G2ElementT,
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bls24315.NewG2AffineFixed(deltaNeg)
s.G2.GammaNeg = sw_bls24315.NewG2AffineFixed(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKeyFixed[sw_bls24315.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bls24315.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKeyFixed[sw_bls24315.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
case *VerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]:
tVk, ok := vk.(*groth16backend_bw6761.VerifyingKey)
@@ -440,9 +468,12 @@ func ValueOfVerifyingKeyFixed[G1El algebra.G1ElementT, G2El algebra.G2ElementT,
gammaNeg.Neg(&tVk.G2.Gamma)
s.G2.DeltaNeg = sw_bw6761.NewG2AffineFixed(deltaNeg)
s.G2.GammaNeg = sw_bw6761.NewG2AffineFixed(gammaNeg)
- s.CommitmentKey, err = pedersen.ValueOfVerifyingKeyFixed[sw_bw6761.G2Affine](&tVk.CommitmentKey)
- if err != nil {
- return ret, fmt.Errorf("commitment key: %w", err)
+ s.CommitmentKeys = make([]pedersen.VerifyingKey[sw_bw6761.G2Affine], len(tVk.CommitmentKeys))
+ for i := range tVk.CommitmentKeys {
+ s.CommitmentKeys[i], err = pedersen.ValueOfVerifyingKeyFixed[sw_bw6761.G2Affine](&tVk.CommitmentKeys[i])
+ if err != nil {
+ return ret, fmt.Errorf("commitment key[%d]: %w", i, err)
+ }
}
default:
return ret, fmt.Errorf("unknown parametric type combination")
@@ -567,6 +598,12 @@ func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.
// AssertProof asserts that the SNARK proof holds for the given witness and
// verifying key.
func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El, GtEl], proof Proof[G1El, G2El], witness Witness[FR], opts ...VerifierOption) error {
+ if len(vk.CommitmentKeys) != len(proof.Commitments) {
+ return fmt.Errorf("invalid number of commitments, got %d, expected %d", len(proof.Commitments), len(vk.CommitmentKeys))
+ }
+ if len(vk.CommitmentKeys) != len(vk.PublicAndCommitmentCommitted) {
+ return fmt.Errorf("invalid number of commitment keys, got %d, expected %d", len(vk.CommitmentKeys), len(vk.PublicAndCommitmentCommitted))
+ }
var fr FR
nbPublicVars := len(vk.G1.K) - len(vk.PublicAndCommitmentCommitted)
if len(witness.Public) != nbPublicVars-1 {
@@ -612,15 +649,16 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[G1El, G2El,
commitmentAuxData[i] = res
}
- if len(vk.PublicAndCommitmentCommitted) > 0 {
- folded, err := v.commitment.FoldCommitments(proof.Commitments, commitmentAuxData...)
- if err != nil {
- return fmt.Errorf("fold commitments: %w", err)
- }
- err = v.commitment.AssertCommitment(folded, proof.CommitmentPok, vk.CommitmentKey, opt.pedopt...)
- if err != nil {
+ switch len(vk.CommitmentKeys) {
+ case 0:
+ // explicitly do not verify the commitment as there is nothing
+ case 1:
+ if err = v.commitment.AssertCommitment(proof.Commitments[0], proof.CommitmentPok, vk.CommitmentKeys[0], opt.pedopt...); err != nil {
return fmt.Errorf("assert commitment: %w", err)
}
+ default:
+ // TODO: we support only a single commitment in the recursion for now
+ return fmt.Errorf("multiple commitments are not supported")
}
kSum, err := v.curve.MultiScalarMul(inP, inS, opt.algopt...)
diff --git a/test/assert_checkcircuit.go b/test/assert_checkcircuit.go
index d75700513b..15789da5fb 100644
--- a/test/assert_checkcircuit.go
+++ b/test/assert_checkcircuit.go
@@ -1,8 +1,6 @@
package test
import (
- "crypto/sha256"
-
"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16"
@@ -12,6 +10,7 @@ import (
"github.com/consensys/gnark/constraint"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/schema"
+ "github.com/consensys/gnark/logger"
"github.com/consensys/gnark/test/unsafekzg"
)
@@ -33,6 +32,7 @@ import (
func (assert *Assert) CheckCircuit(circuit frontend.Circuit, opts ...TestingOption) {
// get the testing configuration
opt := assert.options(opts...)
+ log := logger.Logger()
// for each {curve, backend} tuple
for _, curve := range opt.curves {
@@ -127,10 +127,15 @@ func (assert *Assert) CheckCircuit(circuit frontend.Circuit, opts ...TestingOpti
verifierOpts := opt.verifierOpts
if b == backend.GROTH16 {
// currently groth16 Solidity checker only supports circuits with up to 1 commitment
+ if len(ccs.GetCommitments().CommitmentIndexes()) > 1 {
+ log.Warn().
+ Int("nb_commitments", len(ccs.GetCommitments().CommitmentIndexes())).
+ Msg("skipping solidity check, too many commitments")
+ }
checkSolidity = checkSolidity && (len(ccs.GetCommitments().CommitmentIndexes()) <= 1)
- // additionally, we use sha256 as hash to field (fixed in Solidity contract)
- proverOpts = append(proverOpts, backend.WithProverHashToFieldFunction(sha256.New()))
- verifierOpts = append(verifierOpts, backend.WithVerifierHashToFieldFunction(sha256.New()))
+ // set the default hash function in case of custom hash function not set. This is to ensure that the proof can be verified by gnark-solidity-checker
+ proverOpts = append([]backend.ProverOption{solidity.WithProverTargetSolidityVerifier(b)}, opt.proverOpts...)
+ verifierOpts = append([]backend.VerifierOption{solidity.WithVerifierTargetSolidityVerifier(b)}, opt.verifierOpts...)
}
proof, err := concreteBackend.prove(ccs, pk, w.full, proverOpts...)
assert.noError(err, &w)
diff --git a/test/assert_solidity.go b/test/assert_solidity.go
index f72818aae9..8949aa36a5 100644
--- a/test/assert_solidity.go
+++ b/test/assert_solidity.go
@@ -26,11 +26,6 @@ func (assert *Assert) solidityVerification(b backend.ID, vk solidity.VerifyingKe
}
assert.t.Helper()
- // set default options for CI when none are provided
- if len(opts) == 0 {
- opts = append(opts, solidity.WithPragmaVersion("^0.8.0")) // to avoid needing sync Solidity CI all the time
- }
-
// make temp dir
tmpDir, err := os.MkdirTemp("", "gnark-solidity-check*")
assert.NoError(err)