diff --git a/circuits/test/statetransition/statetransition_inputs.go b/circuits/test/statetransition/statetransition_inputs.go new file mode 100644 index 0000000..5387fe8 --- /dev/null +++ b/circuits/test/statetransition/statetransition_inputs.go @@ -0,0 +1,177 @@ +package statetransitiontest + +import ( + "fmt" + "math" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + fr_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + bw6761mimc "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/mimc" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/std/algebra/native/sw_bls12377" + "github.com/consensys/gnark/std/math/emulated/emparams" + stdgroth16 "github.com/consensys/gnark/std/recursion/groth16" + "github.com/vocdoni/vocdoni-z-sandbox/circuits" + "github.com/vocdoni/vocdoni-z-sandbox/circuits/aggregator" + "github.com/vocdoni/vocdoni-z-sandbox/circuits/statetransition" + aggregatortest "github.com/vocdoni/vocdoni-z-sandbox/circuits/test/aggregator" + ballottest "github.com/vocdoni/vocdoni-z-sandbox/circuits/test/ballotproof" +) + +// StateTransitionTestResults struct includes relevant data after StateTransitionCircuit +// inputs generation, including the encrypted ballots in both formats: matrix +// and plain (for hashing) +type StateTransitionTestResults struct { + ProcessId []byte + CensusRoot *big.Int + EncryptionPubKey [2]*big.Int + Nullifiers []*big.Int + Commitments []*big.Int + Addresses []*big.Int + EncryptedBallots [][ballottest.NFields][2][2]*big.Int + PlainEncryptedBallots []*big.Int +} + +// StateTransitionInputsForTest returns the StateTransitionTestResults, the placeholder +// and the assigments of a StateTransitionCircuit for the processId provided +// generating nValidVoters. If something fails it returns an error. +func StateTransitionInputsForTest(processId []byte, nValidVoters int) ( + *StateTransitionTestResults, *statetransition.Circuit, *statetransition.Circuit, error, +) { + // generate aggregator circuit and inputs + agInputs, agPlaceholder, agWitness, err := aggregatortest.AggregarorInputsForTest(processId, nValidVoters) + if err != nil { + return nil, nil, nil, err + } + // compile aggregoar circuit + agCCS, err := frontend.Compile(ecc.BLS12_377.ScalarField(), r1cs.NewBuilder, agPlaceholder) + if err != nil { + return nil, nil, nil, err + } + agPk, agVk, err := groth16.Setup(agCCS) + if err != nil { + return nil, nil, nil, err + } + // generate voters proofs + proofInBLS12377 := stdgroth16.Proof[sw_bls12377.G1Affine, sw_bls12377.G2Affine]{} + pubInputs := stdgroth16.Witness[emparams.BLS12377Fr]{} + // parse the witness to the circuit + fullWitness, err := frontend.NewWitness(agWitness, ecc.BLS12_377.ScalarField()) + if err != nil { + return nil, nil, nil, err + } + // generate the proof + proof, err := groth16.Prove(agCCS, agPk, fullWitness, stdgroth16.GetNativeProverOptions(ecc.BW6_761.ScalarField(), ecc.BLS12_377.ScalarField())) + if err != nil { + return nil, nil, nil, fmt.Errorf("err proving proof: %w", err) + } + // convert the proof to the circuit proof type + proofInBLS12377, err = stdgroth16.ValueOfProof[sw_bls12377.G1Affine, sw_bls12377.G2Affine](proof) + if err != nil { + return nil, nil, nil, err + } + // convert the public inputs to the circuit public inputs type + publicWitness, err := fullWitness.Public() + if err != nil { + return nil, nil, nil, err + } + err = groth16.Verify(proof, agVk, publicWitness, stdgroth16.GetNativeVerifierOptions(ecc.BW6_761.ScalarField(), ecc.BLS12_377.ScalarField())) + if err != nil { + return nil, nil, nil, err + } + pubInputs, err = stdgroth16.ValueOfWitness[sw_bls12377.ScalarField](publicWitness) + if err != nil { + return nil, nil, nil, err + } + // compute public inputs hash + inputs := []*big.Int{ + new(big.Int).SetBytes(agInputs.ProcessId), + agInputs.CensusRoot, + // ballotMode(), // TODO: serialize ballotmode into a bigint? or flatten it? + agInputs.EncryptionPubKey[0], + agInputs.EncryptionPubKey[1], + } + // pad voters inputs (nullifiers, commitments, addresses, plain EncryptedBallots) + nullifiers := circuits.BigIntArrayToN(agInputs.Nullifiers, aggregator.MaxVotes) + commitments := circuits.BigIntArrayToN(agInputs.Commitments, aggregator.MaxVotes) + addresses := circuits.BigIntArrayToN(agInputs.Addresses, aggregator.MaxVotes) + plainEncryptedBallots := circuits.BigIntArrayToN(agInputs.PlainEncryptedBallots, aggregator.MaxVotes*ballottest.NFields*4) + // append voters inputs (nullifiers, commitments, addresses, plain EncryptedBallots) + inputs = append(inputs, nullifiers...) + inputs = append(inputs, commitments...) + inputs = append(inputs, addresses...) + inputs = append(inputs, plainEncryptedBallots...) + // hash the inputs to generate the inputs hash + var buf [fr_bw6761.Bytes]byte + aggregatorHashFn := bw6761mimc.NewMiMC() + for _, input := range inputs { + input.FillBytes(buf[:]) + _, err := aggregatorHashFn.Write(buf[:]) + if err != nil { + return nil, nil, nil, err + } + } + publicHash := new(big.Int).SetBytes(aggregatorHashFn.Sum(nil)) + // init final assigments stuff + finalAssigments := &statetransition.Circuit{ + InputsHash: publicHash, + ValidVotes: aggregator.EncodeProofsSelector(nValidVoters), + BallotMode: circuits.BallotMode[frontend.Variable]{ + MaxCount: ballottest.MaxCount, + ForceUniqueness: ballottest.ForceUniqueness, + MaxValue: ballottest.MaxValue, + MinValue: ballottest.MinValue, + MaxTotalCost: int(math.Pow(float64(ballottest.MaxValue), float64(ballottest.CostExp))) * ballottest.MaxCount, + MinTotalCost: ballottest.MaxCount, + CostExp: ballottest.CostExp, + CostFromWeight: ballottest.CostFromWeight, + EncryptionPubKey: [2]frontend.Variable{agInputs.EncryptionPubKey[0], agInputs.EncryptionPubKey[1]}, + }, + ProcessId: new(big.Int).SetBytes(agInputs.ProcessId), + CensusRoot: agInputs.CensusRoot, + VerifyProofs: proofInBLS12377, + VerifyPublicInputs: pubInputs, + } + // set voters final witness stuff + for i := 0; i < nValidVoters; i++ { + finalAssigments.Nullifiers[i] = agInputs.Nullifiers[i] + finalAssigments.Commitments[i] = agInputs.Commitments[i] + finalAssigments.Addresses[i] = new(big.Int).SetBytes(vvData[i].Address.Bytes()) + for j := 0; j < ballottest.NFields; j++ { + for n := 0; n < 2; n++ { + for m := 0; m < 2; m++ { + finalAssigments.EncryptedBallots[i][j][n][m] = agInputs.EncryptedBallots[i][j][n][m] + } + } + } + } + // create final placeholder + finalPlaceholder := &statetransition.Circuit{ + AggregatedProofWitness: stdgroth16.Witness[sw_bls12377.ScalarField]{}, + AggregatedProof: stdgroth16.Proof[sw_bls12377.G1Affine, sw_bls12377.G2Affine]{}, + AggregatedProofVK: stdgroth16.VerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{}, + } + // fix the vote verifier verification key + fixedVk, err := stdgroth16.ValueOfVerifyingKeyFixed[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT](agVk) + if err != nil { + return nil, nil, nil, err + } + finalPlaceholder.AggregatedProofVK = fixedVk + // // fill placeholder and witness with dummy circuits + // if err := aggregator.FillWithDummyFixed(finalPlaceholder, finalAssigments, agCCS, nValidVoters); err != nil { + // return nil, nil, nil, err + // } + return &StateTransitionTestResults{ + ProcessId: agInputs.ProcessId, + CensusRoot: agInputs.CensusRoot, + EncryptionPubKey: agInputs.EncryptionPubKey, + Nullifiers: nullifiers, + Commitments: commitments, + Addresses: addresses, + EncryptedBallots: agInputs.EncryptedBallots, + PlainEncryptedBallots: plainEncryptedBallots, + }, finalPlaceholder, finalAssigments, nil +} diff --git a/circuits/test/statetransition/statetransition_test.go b/circuits/test/statetransition/statetransition_test.go new file mode 100644 index 0000000..b5090d1 --- /dev/null +++ b/circuits/test/statetransition/statetransition_test.go @@ -0,0 +1,34 @@ +package statetransitiontest + +import ( + "os" + "testing" + "time" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + stdgroth16 "github.com/consensys/gnark/std/recursion/groth16" + "github.com/consensys/gnark/test" + qt "github.com/frankban/quicktest" + "github.com/vocdoni/vocdoni-z-sandbox/util" +) + +func TestStateTransitionCircuit(t *testing.T) { + if os.Getenv("RUN_CIRCUIT_TESTS") == "" || os.Getenv("RUN_CIRCUIT_TESTS") == "false" { + t.Skip("skipping circuit tests...") + } + c := qt.New(t) + // inputs generation + now := time.Now() + processId := util.RandomBytes(20) + _, placeholder, witness, err := StateTransitionInputsForTest(processId, 3) + c.Assert(err, qt.IsNil) + c.Logf("inputs generation took %s", time.Since(now).String()) + // proving + now = time.Now() + assert := test.NewAssert(t) + assert.SolvingSucceeded(placeholder, witness, + test.WithCurves(ecc.BW6_761), test.WithBackends(backend.GROTH16), + test.WithProverOpts(stdgroth16.GetNativeProverOptions(ecc.BN254.ScalarField(), ecc.BW6_761.ScalarField()))) + c.Logf("proving took %s", time.Since(now).String()) +}