@@ -3,7 +3,7 @@ package groth16
33import (
44 "bytes"
55
6- "github.com/consensys/gnark-crypto/ecc/bn254/fr "
6+ "github.com/consensys/gnark-crypto/ecc/bn254/fp "
77)
88
99// solidityTemplate
@@ -25,8 +25,9 @@ pragma solidity {{ .Cfg.PragmaVersion }};
2525/// @title Groth16 verifier template.
2626/// @author Remco Bloemen
2727/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed
28- /// (256 bytes) and compressed (128 bytes) format. A view function is provided
29- /// to compress proofs.
28+ /// (256 bytes + optional commitments) and compressed (128 bytes) format.
29+ /// Uncompressed proofs are passed as bytes calldata matching the output of
30+ /// MarshalSolidity(). A view function is provided to compress proofs.
3031/// @notice See <https://2π.com/23/bn254-compression> for further explanation.
3132contract Verifier{{ .Cfg.InterfaceDeclaration }} {
3233
@@ -479,11 +480,11 @@ contract Verifier{{ .Cfg.InterfaceDeclaration }} {
479480 /// Compress a proof.
480481 /// @notice Will revert with InvalidProof if the curve points are invalid,
481482 /// but does not verify the proof itself.
482- /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for
483- /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197 .
483+ /// @param proof The uncompressed Groth16 proof. Points (A, B, C) encoded as in EIP-197
484+ /// (256 bytes total) .
484485 {{- if gt $numCommitments 0 }}
485- /// @param commitments Pedersen commitments from the proof.
486- /// @param commitmentPok proof of knowledge for the Pedersen commitments .
486+ /// Followed by Pedersen commitments ({{$numCommitments}} × 64 bytes) and proof of knowledge
487+ /// (64 bytes) = {{ sum 256 (mul (sum $numCommitments 1) 64) }} bytes total .
487488 {{- end }}
488489 /// @return compressed The compressed proof. Elements are in the same order as for
489490 /// verifyCompressedProof. I.e. points (A, B, C) in compressed format.
@@ -492,28 +493,54 @@ contract Verifier{{ .Cfg.InterfaceDeclaration }} {
492493 /// @return compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments.
493494 {{- end }}
494495 {{- if eq $numCommitments 0 }}
495- function compressProof(uint256[8] calldata proof)
496+ function compressProof(bytes calldata proof)
496497 public view returns (uint256[4] memory compressed) {
498+ require(proof.length == 256, "invalid proof length");
497499 {{- else }}
498- function compressProof(
499- uint256[8] calldata proof,
500- uint256[{{mul 2 $numCommitments}}] calldata commitments,
501- uint256[2] calldata commitmentPok
502- )
500+ function compressProof(bytes calldata proof)
503501 public view returns (
504502 uint256[4] memory compressed,
505503 uint256[{{$numCommitments}}] memory compressedCommitments,
506504 uint256 compressedCommitmentPok
507505 ) {
506+ require(proof.length == {{ sum 256 (mul (sum $numCommitments 1) 64) }}, "invalid proof length");
508507 {{- end }}
509- compressed[0] = compress_g1(proof[0], proof[1]);
510- (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]);
511- compressed[3] = compress_g1(proof[6], proof[7]);
508+ uint256 a0;
509+ uint256 a1;
510+ assembly ("memory-safe") {
511+ a0 := calldataload(proof.offset)
512+ a1 := calldataload(add(proof.offset, 0x20))
513+ }
514+ compressed[0] = compress_g1(a0, a1);
515+ assembly ("memory-safe") {
516+ a0 := calldataload(add(proof.offset, 0x60))
517+ a1 := calldataload(add(proof.offset, 0x40))
518+ }
519+ uint256 b0;
520+ uint256 b1;
521+ assembly ("memory-safe") {
522+ b0 := calldataload(add(proof.offset, 0xa0))
523+ b1 := calldataload(add(proof.offset, 0x80))
524+ }
525+ (compressed[2], compressed[1]) = compress_g2(a0, a1, b0, b1);
526+ assembly ("memory-safe") {
527+ a0 := calldataload(add(proof.offset, 0xc0))
528+ a1 := calldataload(add(proof.offset, 0xe0))
529+ }
530+ compressed[3] = compress_g1(a0, a1);
512531 {{- if gt $numCommitments 0 }}
513532 {{- range $i := intRange $numCommitments }}
514- compressedCommitments[{{$i}}] = compress_g1(commitments[{{mul 2 $i}}], commitments[{{sum (mul 2 $i) 1}}]);
533+ assembly ("memory-safe") {
534+ a0 := calldataload(add(proof.offset, {{ hex (sum 0x100 (mul $i 0x40)) }}))
535+ a1 := calldataload(add(proof.offset, {{ hex (sum 0x120 (mul $i 0x40)) }}))
536+ }
537+ compressedCommitments[{{$i}}] = compress_g1(a0, a1);
515538 {{- end }}
516- compressedCommitmentPok = compress_g1(commitmentPok[0], commitmentPok[1]);
539+ assembly ("memory-safe") {
540+ a0 := calldataload(add(proof.offset, {{ hex (sum 0x100 (mul $numCommitments 0x40)) }}))
541+ a1 := calldataload(add(proof.offset, {{ hex (sum 0x120 (mul $numCommitments 0x40)) }}))
542+ }
543+ compressedCommitmentPok = compress_g1(a0, a1);
517544 {{- end }}
518545 }
519546
@@ -682,25 +709,33 @@ contract Verifier{{ .Cfg.InterfaceDeclaration }} {
682709 /// with PublicInputNotInField the public input is not reduced.
683710 /// @notice There is no return value. If the function does not revert, the
684711 /// proof was successfully verified.
685- /// @param proof the points (A, B, C) in EIP-197 format matching the output
686- /// of compressProof .
712+ /// @param proof the serialized proof, containing the points (A, B, C) in EIP-197 format
713+ /// (256 bytes total) .
687714 {{- if gt $numCommitments 0 }}
688- /// @param commitments the Pedersen commitments from the proof.
689- /// @param commitmentPok the proof of knowledge for the Pedersen commitments .
715+ /// Followed by Pedersen commitments ({{$numCommitments}} × 64 bytes) and proof of knowledge
716+ /// (64 bytes) = {{ sum 256 (mul (sum $numCommitments 1) 64) }} bytes total .
690717 {{- end }}
691718 /// @param input the public input field elements in the scalar field Fr.
692719 /// Elements must be reduced.
693720 function verifyProof(
694- uint256[8] calldata proof,
695- {{- if gt $numCommitments 0}}
696- uint256[{{mul 2 $numCommitments}}] calldata commitments,
697- uint256[2] calldata commitmentPok,
698- {{- end }}
721+ bytes calldata proof,
699722 uint256[{{$numWitness}}] calldata input
700723 ) public view {
724+ {{- if gt $numCommitments 0 }}
725+ require(proof.length == {{ sum 256 (mul (sum $numCommitments 1) 64) }}, "invalid proof length");
726+ {{- else }}
727+ require(proof.length == 256, "invalid proof length");
728+ {{- end }}
729+
701730 {{- if eq $numCommitments 0 }}
702731 (uint256 x, uint256 y) = publicInputMSM(input);
703732 {{- else }}
733+ // Copy commitment points from proof bytes into memory for publicInputMSM
734+ uint256[{{mul 2 $numCommitments}}] memory commitments;
735+ assembly ("memory-safe") {
736+ calldatacopy(commitments, add(proof.offset, 0x100), {{ mul $numCommitments 0x40 }})
737+ }
738+
704739 // HashToField
705740 uint256[{{$numCommitments}}] memory publicCommitments;
706741 uint256[] memory publicAndCommitmentCommitted;
@@ -726,28 +761,26 @@ contract Verifier{{ .Cfg.InterfaceDeclaration }} {
726761 }
727762 {{- end }}
728763
729- publicCommitments[{{$i}}] = uint256(
730- {{ hashFnName }}(
731- abi.encodePacked(
732- commitments[{{mul $i 2}}],
733- commitments[{{sum (mul $i 2) 1}}],
734- publicAndCommitmentCommitted
735- )
736- )
737- ) % R;
764+ {
765+ bytes memory hashInput = abi.encodePacked(
766+ proof[{{ hex (sum 0x100 (mul $i 0x40)) }}:{{ hex (sum 0x140 (mul $i 0x40)) }}],
767+ publicAndCommitmentCommitted
768+ );
769+ publicCommitments[{{$i}}] = uint256({{ hashFnName }}(hashInput)) % R;
770+ }
738771 {{- end }}
739772
740773 // Verify pedersen commitments
741774 bool success;
742775 assembly ("memory-safe") {
743776 let f := mload(0x40)
744777
745- calldatacopy(f, commitments, 0x40) // Copy Commitments
778+ calldatacopy(f, add(proof.offset, 0x100), 0x40) // Copy first commitment
746779 mstore(add(f, 0x40), PEDERSEN_GSIGMANEG_X_1)
747780 mstore(add(f, 0x60), PEDERSEN_GSIGMANEG_X_0)
748781 mstore(add(f, 0x80), PEDERSEN_GSIGMANEG_Y_1)
749782 mstore(add(f, 0xa0), PEDERSEN_GSIGMANEG_Y_0)
750- calldatacopy(add(f, 0xc0), commitmentPok, 0x40)
783+ calldatacopy(add(f, 0xc0), add(proof.offset, {{ hex (sum 0x100 (mul $numCommitments 0x40)) }}), 0x40) // Copy PoK
751784 mstore(add(f, 0x100), PEDERSEN_G_X_1)
752785 mstore(add(f, 0x120), PEDERSEN_G_X_0)
753786 mstore(add(f, 0x140), PEDERSEN_G_Y_1)
@@ -778,7 +811,7 @@ contract Verifier{{ .Cfg.InterfaceDeclaration }} {
778811
779812 // Copy points (A, B, C) to memory. They are already in correct encoding.
780813 // This is pairing e(A, B) and G1 of e(C, -δ).
781- calldatacopy(f, proof, 0x100)
814+ calldatacopy(f, proof.offset , 0x100)
782815
783816 // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory.
784817 // OPT: This could be better done using a single codecopy, but
@@ -821,17 +854,30 @@ contract Verifier{{ .Cfg.InterfaceDeclaration }} {
821854
822855// MarshalSolidity converts a proof to a byte array that can be used in a
823856// Solidity contract.
857+ //
858+ // The output format is:
859+ //
860+ // Ar.X (32) | Ar.Y (32) | Bs.X1 (32) | Bs.X0 (32) | Bs.Y1 (32) | Bs.Y0 (32) | Krs.X (32) | Krs.Y (32)
861+ // [Commitment_0.X (32) | Commitment_0.Y (32) | ... | PoK.X (32) | PoK.Y (32)]
862+ //
863+ // This matches the bytes calldata expected by verifyProof and compressProof.
824864func (proof * Proof ) MarshalSolidity () []byte {
825865 var buf bytes.Buffer
826866 _ , err := proof .WriteRawTo (& buf )
827867 if err != nil {
828868 panic (err )
829869 }
830870
831- // If there are no commitments, we can return only Ar | Bs | Krs
832871 if len (proof .Commitments ) > 0 {
833- return buf .Bytes ()
834- } else {
835- return buf .Bytes ()[:8 * fr .Bytes ]
872+ // WriteRawTo encodes: Ar(64) | Bs(128) | Krs(64) | len(4) | Commitments(N×64) | PoK(64)
873+ // We need to strip the 4-byte slice length prefix to get:
874+ // Ar(64) | Bs(128) | Krs(64) | Commitments(N×64) | PoK(64)
875+ raw := buf .Bytes ()
876+ base := 8 * fp .Bytes // 256
877+ result := make ([]byte , 0 , len (raw )- 4 )
878+ result = append (result , raw [:base ]... )
879+ result = append (result , raw [base + 4 :]... )
880+ return result
836881 }
882+ return buf .Bytes ()[:8 * fp .Bytes ]
837883}
0 commit comments