forked from TrueCloudLab/neoneo-go
143 lines
4.8 KiB
Go
143 lines
4.8 KiB
Go
|
package cubic
|
||
|
|
||
|
import (
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/consensys/gnark-crypto/ecc"
|
||
|
"github.com/consensys/gnark/backend/groth16"
|
||
|
"github.com/consensys/gnark/frontend"
|
||
|
"github.com/consensys/gnark/frontend/cs/r1cs"
|
||
|
"github.com/consensys/gnark/test"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/zkpbinding"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
// First of all, you'll need to ensure that your circuit is properly constructed.
|
||
|
// Use unit tests to test execute the circuit and verify it against a various set
|
||
|
// of curves and backends with gnark/test package.
|
||
|
// More about circuit testing using gnark/test package: https://pkg.go.dev/github.com/consensys/gnark/test@v0.7.0
|
||
|
|
||
|
// TestCubicCircuit_TestExecution runs the provided circuit in the test execution engine.
|
||
|
func TestCubicCircuit_TestExecution(t *testing.T) {
|
||
|
var (
|
||
|
circuit CubicCircuit
|
||
|
assignment = CubicCircuit{X: 3, Y: 35}
|
||
|
)
|
||
|
|
||
|
// Test executing the circuit without running a ZK-SNARK prover (with the
|
||
|
// help of test engine). It can be useful for the circuit debugging, see
|
||
|
// https://docs.gnark.consensys.net/HowTo/debug_test#common-errors.
|
||
|
err := test.IsSolved(&circuit, &assignment, ecc.BLS12_381.ScalarField())
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
|
||
|
// TestCubicCircuit_Verification performs the circuit correctness testing over a
|
||
|
// set of all supported curves and backends and over a specified curve with a
|
||
|
// set of exact input and output values.
|
||
|
func TestCubicCircuit_Verification(t *testing.T) {
|
||
|
// Assert object wrapping testing.T.
|
||
|
assert := test.NewAssert(t)
|
||
|
|
||
|
// Declare the circuit.
|
||
|
var cubicCircuit CubicCircuit
|
||
|
|
||
|
// The default behavior of the assert helper is to test the circuit across
|
||
|
// all supported curves and backends, ensure correct serialization, and
|
||
|
// cross-test the constraint system solver against a big.Int test execution
|
||
|
// engine.
|
||
|
assert.ProverFailed(&cubicCircuit, &CubicCircuit{
|
||
|
X: 3, // Wrong value.
|
||
|
Y: 5,
|
||
|
})
|
||
|
|
||
|
// If needed, we can directly specify the desired curves or backends.
|
||
|
assert.ProverSucceeded(&cubicCircuit, &CubicCircuit{
|
||
|
X: 3, // Good value.
|
||
|
Y: 35,
|
||
|
}, test.WithCurves(ecc.BLS12_381))
|
||
|
}
|
||
|
|
||
|
// TestCubicCircuit_EndToEnd shows how to generate proof for pre-defined cubic circuit,
|
||
|
// how to generate Go verification contract that can be compiled by NeoGo and deployed
|
||
|
// to the chain and how to verify proofs via verification contract invocation.
|
||
|
func TestCubicCircuit_EndToEnd(t *testing.T) {
|
||
|
var (
|
||
|
circuit CubicCircuit
|
||
|
assignment = CubicCircuit{X: 3, Y: 35}
|
||
|
)
|
||
|
|
||
|
// Compile our circuit into a R1CS (a constraint system).
|
||
|
ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// One time setup (groth16 zkSNARK).
|
||
|
pk, vk, err := groth16.Setup(ccs)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Intermediate step: witness definition.
|
||
|
witness, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField())
|
||
|
require.NoError(t, err)
|
||
|
publicWitness, err := witness.Public()
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Proof creation (groth16).
|
||
|
proof, err := groth16.Prove(ccs, pk, witness)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Ensure that gnark can successfully verify the proof (just in case).
|
||
|
err = groth16.Verify(proof, vk, publicWitness)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Now, when we're sure that the proof is valid, we can create and deploy verification
|
||
|
// contract to the Neo testing chain.
|
||
|
args, err := zkpbinding.GetVerifyProofArgs(proof, publicWitness)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create contract file.
|
||
|
tmpDir := t.TempDir()
|
||
|
srcPath := filepath.Join(tmpDir, "verify.go")
|
||
|
f, err := os.Create(srcPath)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create contract configuration file.
|
||
|
cfgPath := filepath.Join(tmpDir, "verify.yml")
|
||
|
fCfg, err := os.Create(cfgPath)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create contract go.mod and go.sum files.
|
||
|
fMod, err := os.Create(filepath.Join(tmpDir, "go.mod"))
|
||
|
require.NoError(t, err)
|
||
|
fSum, err := os.Create(filepath.Join(tmpDir, "go.sum"))
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
err = zkpbinding.GenerateVerifier(zkpbinding.Config{
|
||
|
VerifyingKey: vk,
|
||
|
Output: f,
|
||
|
CfgOutput: fCfg,
|
||
|
GomodOutput: fMod,
|
||
|
GosumOutput: fSum,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
require.NoError(t, f.Close())
|
||
|
require.NoError(t, fCfg.Close())
|
||
|
require.NoError(t, fMod.Close())
|
||
|
require.NoError(t, fSum.Close())
|
||
|
|
||
|
// Create testing chain and deploy contract onto it.
|
||
|
bc, committee := chain.NewSingle(t)
|
||
|
e := neotest.NewExecutor(t, bc, committee, committee)
|
||
|
|
||
|
// Compile verification contract and deploy the contract onto chain.
|
||
|
c := neotest.CompileFile(t, e.Validator.ScriptHash(), srcPath, cfgPath)
|
||
|
e.DeployContract(t, c, nil)
|
||
|
|
||
|
// Verify proof via verification contract call.
|
||
|
validatorInvoker := e.ValidatorInvoker(c.Hash)
|
||
|
validatorInvoker.Invoke(t, true, "verifyProof", args.A, args.B, args.C, args.PublicWitnesses)
|
||
|
}
|