Skip to content

Commit 147ac71

Browse files
ThomasPiellardThomas PIELLARDivokub
authored
Feat/solidity verifier bls12381 (#1554)
Co-authored-by: Thomas PIELLARD <thomaspiellard@Thomass-MacBook-Pro.local> Co-authored-by: Ivo Kubjas <ivo.kubjas@consensys.net>
1 parent 682b50c commit 147ac71

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+9045
-392
lines changed

.github/workflows/pr.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ jobs:
6363

6464
- name: install deps
6565
run: |
66-
go install github.com/consensys/gnark-solidity-checker@v0.1.0
6766
go install github.com/ethereum/go-ethereum/cmd/abigen@v1.14.8
6867
sudo add-apt-repository ppa:ethereum/ethereum
6968
sudo apt-get update

.github/workflows/push.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ jobs:
7878
- name: install solc deps
7979
if: startsWith(matrix.os, 'gha-runner-scale-set-ubuntu') == true
8080
run: |
81-
go install github.com/consensys/gnark-solidity-checker@v0.1.0
8281
sudo add-apt-repository ppa:ethereum/ethereum
8382
sudo apt-get update
8483
sudo apt-get install -y --no-install-recommends solc

backend/groth16/bls12-381/solidity.go

Lines changed: 909 additions & 0 deletions
Large diffs are not rendered by default.

backend/groth16/bls12-381/verify.go

Lines changed: 110 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/groth16/bn254/solidity.go

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package groth16
33
import (
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.
3132
contract 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.
824864
func (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
}

backend/groth16/bn254/verify.go

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)