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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", true, }, { - "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", false, }, { - "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", false, }, { - "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", "", true, }, { - "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", true, }, { - "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", true, }, { - "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", true, }, { - "pQUlLSBu9HmVa9hB0rEu1weeBv2RKQQ8yCHpwXTHeSkcQqmSOuzednF8o0+MdyNupQUlLSBu9HmVa9hB0rEu1weeBv2RKQQ8yCHpwXTHeSkcQqmSOuzednF8o0+MdyNuhKgxmPN2c94UBtlYc0kZS6CwyMEEV/nVGSjajEZPdnpbK7fEcPd0hWNcOxKWq8qBBPfT69Ore74buf8C26ZTyKnjgMsGCvoDAMOsA07DjjQ1nIkkwIGFFUT3iMO83TdEpWgV/2z7WT9axNH/QFPOjXvwQJFnC7hLxHnX6pgKOdAaioKdi6FX3Y2SwWEO3UuxFd3KwsrZ2+mma/W3KP/cPpSzqyHa5VaJwOCw6vSM4wHSGKmDF4TSrrnMxzIYiTbTpQUlLSBu9HmVa9hB0rEu1weeBv2RKQQ8yCHpwXTHeSkcQqmSOuzednF8o0+MdyNulrwLi5GjMxD6BKzMMN9+7xFuO7txLCEIhGrIMFIvqTw1QFAO4rmAgyG+ljlYTfWHAkzqvImL1o8dMHhGOTsMLLMg39KsZVqalZwwL3ckpdAf81OJJeWCpCuaSgSXnWhJAAAAEph8ULgPc1Ia5pUdcBzvXnoB4f6dNaLD9MVNN62NaBqJzmdvGnGBujEjn2QZCk/jaKjnBFrS+EQj+rewVlx4CFJpYQhI/6cDVcfdlXN2cxPMzId1NfeiAh800mc9KzMCZJk9JdZu0HbwaalHqgscl4GPumn6rHQHRo2XrlDjwdkQ2ptwpto9meVcoL3SASNdqpSKBAYZ64QscekzfssIpXyNmgY807Z9KwnuyAPbGLXGJ910qKFO0wTQd/TvHGxoJ5hmEMoMQbEPxJo9igwqkOANTEZ0nt6urUIY06Kg4x0VxCs5VpGv+PoVjZyaYnKvy5k948Qh/f8q3vKhVF8vh6tsnIrY7966IMPocl5St6SKEJg7JCZ6gZN4cYrI90EK0Ir9Oj7m8kmOZUSBGJQo6+Y/AIZfYBbzfttnCIdYhorsAcoT4xg4D+Ye/MWVwgaCXpBGD3CNgtC7QXIeaWQvyaUtZBfZQS53aHYSJbrRK95pCqIUfg/3MzfxU3cVNm/NkKn2Th3Puq79m4hF8vAHaADMpI9XbCMO/3eTPF3ID4lMzZKOB+qdNkbdkdNTDmG6E6IGkB/JclOqPHPojkURhKGQ06uIbQvkGuwF06Hb0pht9yK8CVRjigzLb1iNVWHYVrN9kgdFtXfgaxW9DmwRrM/lJ+z/lfKnqwjKrvdOgZG43VprCmykARQvP2A03UovdqyKtSElEFp/PAIFv6vruij8cm1ORGYGwPhGcAgwejMgTYR3KwL1RXl/pI9UWNRsdZMwhN5XbE9+7Am2shbcjDGy+oA0AqE2nSV/44bPcIKdHWbo8DpNFnn4YMtQVB15f6vtp1wCj7yppYulqO/6WK/6tdxnLI+2e1kilZ+BZuF35CQ+tquqWgsTudQZSUBHJ6TTyku/s44ZkJU0YhK8g/L3uykM5NtHm+E4CDEdYSOaZ0Joxnk+esWckqdpw52A7KrJ1webkGPJcn+iGAvzx8xG960sfdZNGRLucOSDK1SvKLTc2R61LjNGj3SJqS0CeKhIL5nszkaXFAquEkafWxpd/8s1xObVmYJ90OpF8oxTIbvn6E2MtTVfhyWySNZ2DI3k693/kcUqYSGFsjGe7A90YA80ZOYkKg9SfvK3TiGZYjm365lmq6PwQcTb3dXzwAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "pQUlLSBu9HmVa9hB0rEu1weeBv2RKQQ8yCHpwXTHeSkcQqmSOuzednF8o0+MdyNupQUlLSBu9HmVa9hB0rEu1weeBv2RKQQ8yCHpwXTHeSkcQqmSOuzednF8o0+MdyNuhKgxmPN2c94UBtlYc0kZS6CwyMEEV/nVGSjajEZPdnpbK7fEcPd0hWNcOxKWq8qBBPfT69Ore74buf8C26ZTyKnjgMsGCvoDAMOsA07DjjQ1nIkkwIGFFUT3iMO83TdEpWgV/2z7WT9axNH/QFPOjXvwQJFnC7hLxHnX6pgKOdAaioKdi6FX3Y2SwWEO3UuxFd3KwsrZ2+mma/W3KP/cPpSzqyHa5VaJwOCw6vSM4wHSGKmDF4TSrrnMxzIYiTbTpQUlLSBu9HmVa9hB0rEu1weeBv2RKQQ8yCHpwXTHeSkcQqmSOuzednF8o0+MdyNulrwLi5GjMxD6BKzMMN9+7xFuO7txLCEIhGrIMFIvqTw1QFAO4rmAgyG+ljlYTfWHAkzqvImL1o8dMHhGOTsMLLMg39KsZVqalZwwL3ckpdAf81OJJeWCpCuaSgSXnWhJAAAAEph8ULgPc1Ia5pUdcBzvXnoB4f6dNaLD9MVNN62NaBqJzmdvGnGBujEjn2QZCk/jaKjnBFrS+EQj+rewVlx4CFJpYQhI/6cDVcfdlXN2cxPMzId1NfeiAh800mc9KzMCZJk9JdZu0HbwaalHqgscl4GPumn6rHQHRo2XrlDjwdkQ2ptwpto9meVcoL3SASNdqpSKBAYZ64QscekzfssIpXyNmgY807Z9KwnuyAPbGLXGJ910qKFO0wTQd/TvHGxoJ5hmEMoMQbEPxJo9igwqkOANTEZ0nt6urUIY06Kg4x0VxCs5VpGv+PoVjZyaYnKvy5k948Qh/f8q3vKhVF8vh6tsnIrY7966IMPocl5St6SKEJg7JCZ6gZN4cYrI90EK0Ir9Oj7m8kmOZUSBGJQo6+Y/AIZfYBbzfttnCIdYhorsAcoT4xg4D+Ye/MWVwgaCXpBGD3CNgtC7QXIeaWQvyaUtZBfZQS53aHYSJbrRK95pCqIUfg/3MzfxU3cVNm/NkKn2Th3Puq79m4hF8vAHaADMpI9XbCMO/3eTPF3ID4lMzZKOB+qdNkbdkdNTDmG6E6IGkB/JclOqPHPojkURhKGQ06uIbQvkGuwF06Hb0pht9yK8CVRjigzLb1iNVWHYVrN9kgdFtXfgaxW9DmwRrM/lJ+z/lfKnqwjKrvdOgZG43VprCmykARQvP2A03UovdqyKtSElEFp/PAIFv6vruij8cm1ORGYGwPhGcAgwejMgTYR3KwL1RXl/pI9UWNRsdZMwhN5XbE9+7Am2shbcjDGy+oA0AqE2nSV/44bPcIKdHWbo8DpNFnn4YMtQVB15f6vtp1wCj7yppYulqO/6WK/6tdxnLI+2e1kilZ+BZuF35CQ+tquqWgsTudQZSUBHJ6TTyku/s44ZkJU0YhK8g/L3uykM5NtHm+E4CDEdYSOaZ0Joxnk+esWckqdpw52A7KrJ1webkGPJcn+iGAvzx8xG960sfdZNGRLucOSDK1SvKLTc2R61LjNGj3SJqS0CeKhIL5nszkaXFAquEkafWxpd/8s1xObVmYJ90OpF8oxTIbvn6E2MtTVfhyWySNZ2DI3k693/kcUqYSGFsjGe7A90YA80ZOYkKg9SfvK3TiGZYjm365lmq6PwQcTb3dXzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "qV2FNaBFqWeL6n9q9OUbCSTcIQvwO0vfaA/f/SxEtLSIaOGIOx8r+WVGFdxmC6i3oOaoEkJWvML7PpKBDtqiK7pKDIaMV5PkV/kQl6UgxZv9OInTwpVPtYcgeeTokG/eBi1qKzJwDoEHVqKeLqrLXJHXhBVQLdoIUOeKj8YMkagVniO9EtK0fW0/9QnRIxXoilxSj5HBEpYwFBitJXRk1ftFGWZFxJXU5PXdRmC+pomyo5Scx+UJQ2NLRWHjKlV0", "aZ8tqrOeEJKt4AMqiRF/WJhIKTDC0HeDTgiJVLZ8OEtiLNj7hflFeVnNXPguxyoqkI/V7pGJtXBpH5N+RswQNA0b23aM33aH0HKHOWoGY/T/L7TQzYFGJ3vTLiXDFZg1OVqkGOMvqAgonOrHGi6IgcALyUMyCKlL5BQY23SeILJpYKolybJNwJfbjxpg0Oz+D2fr7r9XL1GMvgblu52bVQT1fR8uCRJfSsgA2OGw6k/MpKDCfMcjbR8jnZa8ROEvF4cohm7iV1788Vp2/2bdcEZRQSoaGV8pOmA9EkqzJVRABjkDso40fnQcm2IzjBUOsX+uFExVan56/vl9VZVwB0wnee3Uxiredn0kOayiPB16yimxXCDet+M+0UKjmIlmXYpkrCDrH0dn53w+U3OHqMQxPDnUpYBxadM1eI8xWFFxzaLkvega0q0DmEquyY02yiTqo+7Q4qaJVTLgu6/8ekzPxGKRi845NL8gRgaTtM3kidDzIQpyODZD0yeEZDY1M+3sUKHcVkhoxTQBTMyKJPc+M5DeBL3uaWMrvxuL6q8+X0xeBt+9kguPUNtIYqUgPAaXvM2i041bWHTJ0dZLyDJVOyzGaXRaF4mNkAuh4Et6Zw5PuOpMM2mI1oFKEZj7Xqf/yAmy/Le3GfJnMg5vNgE7QxmVsjuKUP28iN8rdi4bUp7c0KJpqLXE6evfRrdZBDRYp+rmOLLDg55ggNuwog==", true, }, { - "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", "", false, }, { - "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", false, }, { - "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", false, }, { - "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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)