// Package cubic describes how to create and verify proofs on the Neo
// blockchain. The example shows how to check that the prover knows the solution
// of the cubic equation: y = x^3 + x + 5. The example is constructed for
// BLS12-381 curve points using Groth-16 proving system. The example includes
// everything that developer needs to start using ZKP on the Neo platform with
// Go SDK:
//  1. The described cubic circuit implementation.
//  2. The off-chain proof generation with the help of gnark-crypto library.
//  3. The Go verification contract generation and deployment with the help of
//     NeoGo library.
//  4. The on-chain proof verification for various sets of input data (implemented
//     as end-to-end test).
//  5. A set of unit-tests aimed to check the circuit validity.
package cubic

import (
	"github.com/consensys/gnark-crypto/ecc"
	"github.com/consensys/gnark/backend/groth16"
	"github.com/consensys/gnark/frontend"
	"github.com/consensys/gnark/frontend/cs/r1cs"
)

// CubicCircuit defines a simple circuit x**3 + x + 5 == y
// that checks that the prover knows the solution for the provided expression.
// The circuit must declare its public and secret inputs as frontend.Variable.
// At compile time, frontend.Compile(...) recursively parses the struct fields
// that contains frontend.Variable to build the frontend.constraintSystem.
// By default, a frontend.Variable has the gnark:",secret" visibility.
type CubicCircuit struct {
	// Struct tags on a variable is optional.
	// Default uses variable name and secret visibility.
	X frontend.Variable `gnark:"x,secret"` // Secret input.
	Y frontend.Variable `gnark:"y,public"` // Public input.
}

// A gnark circuit must implement the frontend.Circuit interface
// (https://docs.gnark.consensys.net/HowTo/write/circuit_structure).
var _ = frontend.Circuit(&CubicCircuit{})

// Define declares the circuit constraints
// x**3 + x + 5 == y.
func (circuit *CubicCircuit) Define(api frontend.API) error {
	x3 := api.Mul(circuit.X, circuit.X, circuit.X)

	// Can be used for the circuit debugging.
	api.Println("X^3", x3)

	api.AssertIsEqual(circuit.Y, api.Add(x3, circuit.X, 5))
	return nil
}

// main demonstrates how to build the proof and verify it with the help of gnark
// library. Error handling omitted intentionally to simplify the example.
func main() { // nolint: unused
	var (
		circuit    CubicCircuit
		assignment = CubicCircuit{X: 3, Y: 35}
	)

	// Compile our circuit into a R1CS (a constraint system).
	ccs, _ := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit)

	// Once the circuit is compiled, you can run the three algorithms of a zk-SNARK back end:

	// 1. One time setup (groth16 zkSNARK).
	pk, vk, _ := groth16.Setup(ccs)

	// Intermediate step: witness definition.
	witness, _ := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField())
	publicWitness, _ := witness.Public()

	// 2. Proof creation (groth16).
	proof, _ := groth16.Prove(ccs, pk, witness)

	// 3. Proof verification (groth16) via gnark-crypto library.
	_ = groth16.Verify(proof, vk, publicWitness)

	// 4. If building ZKP systems for Neo, you'll need a verification contract
	// deployed to the Neo chain to be able to verify generated proofs. This
	// contract can be generated automatically using NeoGo zkpbinding package:
	// err := zkpbinding.GenerateVerifier(zkpbinding.Config{
	//  VerifyingKey: vk,
	// 	Output:       f,    // Verifier Go contract writer
	// 	CfgOutput:    fCfg, // Verifier Go contract configuration YAML file
	// 	GomodOutput:  fMod, // go.mod file for the Verifier contract
	// 	GosumOutput:  fSum, // go.sum file for the Verifier contract
	// })
	//
	// Create arguments to invoke `verifyProof` method of Verifier contract:
	// verifyProofArgs, err := zkpbinding.GetVerifyProofArgs(proof, publicWitness)
	//
	// For end-to-end usage example, please, see the TestCubicCircuit_EndToEnd test.
}
