Disclaimer: AlgoPlonk is a new project and should be used with caution in production environments. Feedback and contributions are welcome as we work to advance the state of zero knowledge proofs on the Algorand blockchain.
AlgoPlonk automatically generates a smart contract verifier from a zk circuit definition. It integrates with the gnark toolchain, so you can use gnark to define a plonk based zk circuit and to generate proofs for it, and use AlgoPlonk to generate an Algorand smart contract verifier that can verify those proofs.
The typical workflow is the following:
- Define and compile a plonk based zk circuit with gnark using the trusted setup provided by AlgoPlonk
- Automatically generate a python Algorand Smart Contract with AlgoPlonk from your compiled circuit
- Compile the python code into the teal files to create the contract with algokit and the puyapy compiler
- Generate proofs and witnesses for your circuit with gnark
- Export proofs and witnesses with AlgoPlonk and generate the method calls to the smart contract verifier to verify them
To ensure compatibility with gnark (and gnark-crypto if you are using it as well), you can pin them to the versions shown in AlgoPlonk's go.mod
file.
AlgoPlonk supports the curves for which the AVM offers elliptic curve operations: BN254 and BLS12-381. Note that at the moment AlgoPlonk does not support custom gates.
A BN254 verifier consumes ~145,000 opcode budget, a BLS12-381 verifier ~185,000. Since these are smart contracts and currently there is a max pooled limit of ~180,000 opcode budget for smart contracts on the AVM, only the BN254 verifier can be used in practice at the moment.
Research is ongoing to see if it is possible to make the verifiers as smart signatures, since they enjoy a max pooled limit of 320,000. Also, the limits on the AVM might increase in the future, making a BLS12-381 smart contract verifier practical.
For now use BN254 verifiers.
AlgoPlonk provides an out of the box trusted setup for both BLS12-381 and bn256 verifiers using the Ethereum KZG Ceremony and the Perpetual Powers of Tau Ceremony, respectively.
The included trusted setup can support circuits with a number of constraints up to 2^14 (16K) for BLS12-381, and 2^17 (128k) for BN254, and the latter could be extended to circuits of up to 128M constraints in the future leveraging additional parameters from the Perpetual Powers of Tau Ceremony.
Check the doc.go
file in the setup package for more details.
AlgoPlonk also provides test-only setups for circuits of any number of gates. These are NOT SUITABLE FOR PRODUCTION.
The examples
folder contains some examples of how to use AlgoPlonk that you can run with go run main.go
in each example subfolder.
Let's follow here the one in examples/basic
, with some added commentary (but we'll be omitting error checking for brevity, check the full example for that).
After the mandatory imports...
package main
import (
"fmt"
"log"
"path/filepath"
"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/frontend"
ap "github.com/giuliop/algoplonk"
"github.com/giuliop/algoplonk/setup"
"github.com/giuliop/algoplonk/testutils"
sdk "github.com/giuliop/algoplonk/testutils/algosdkwrapper"
"github.com/giuliop/algoplonk/verifier"
)
...we define a simple zk circuit with gnark that given public variables a
and b
, verifies that the Prover knows a secret c
that satisfies the Pythagorean equation: a^2 + b^2 == c^2
type BasicCircuit struct {
A frontend.Variable `gnark:",public"`
B frontend.Variable `gnark:",public"`
C frontend.Variable
}
func (circuit *BasicCircuit) Define(api frontend.API) error {
aa := api.Mul(circuit.A, circuit.A)
bb := api.Mul(circuit.B, circuit.B)
cc := api.Mul(circuit.C, circuit.C)
api.AssertIsEqual(api.Add(aa, bb), cc)
return nil
}
Let's also make an assignment that we'll use to generate a proof later
func main() {
var circuit BasicCircuit
// 3*3 + 4*4 == 5*5
var assignment BasicCircuit
assignment.A = 3
assignment.B = 4
assignment.C = 5
A bit of housekeeping now, we specify where to put the automatically generated files and how to call them.
AlgoPlonk will generate these files later on:
- generated/BasicVerifier.py (the smart contract verifier)
- generated/BasicVerifier.proof (input for the verifier)
- generated/BasicVerifier.public_inputs (input for the verifier)
And puyapy will generate these files compiling BasicVerifier.py:
- generated/BasicVerifier.approval.teal
- generated/BasicVerifier.clear.teal
- generated/BasicVerifier.arc32.json
artefactsFolder := "generated"
testutils.CreateDirectoryIfNeeded(artefactsFolder)
verifierName := "BasicVerifier"
puyaVerifierFilename := filepath.Join(artefactsFolder, verifierName+".py")
proofFilename := filepath.Join(artefactsFolder, verifierName+".proof")
publicInputsFilename := filepath.Join(artefactsFolder,
verifierName+".public_inputs")
Let's choose a curve, we use BLS12-381 here, and compile the circuit.
Then we write to file the python code for the smart contract verifier with WritePuyaPyVerifier
and finally we compile it to teal files
curve := ecc.BLS12_381
compiledCircuit, err := ap.Compile(&circuit, curve, setup.Trusted)
err = compiledCircuit.WritePuyaPyVerifier(puyaVerifierFilename)
err = testutils.CompileWithPuyaPy(puyaVerifierFilename, "")
err = testutils.RenamePuyaPyOutput(verifier.VerifierContractName,
verifierName, artefactsFolder)
Cool, let's now deploy the verifier contract on a local blockchain (use algokit localnet start
to activate it).
app_id, err := testutils.DeployArc4AppIfNeeded(verifierName, artefactsFolder)
Yes! We are ready to rock n' roll now, let's create a proof and export it to file together with its public inputs so we can verify it.
verifiedProof, err := compiledCircuit.Verify(&assignment)
err = verifiedProof.ExportProofAndPublicInputs(proofFilename,
publicInputsFilename)
We simulate a call to the verify
method of the verifier contract passing the generated proof and public inputs as parameters.
simulate := true
schema, err := testutils.ReadArc32Schema(filepath.Join(artefactsFolder,
verifierName+".arc32.json"))
result, err := sdk.CallVerifyMethod(app_id, nil, proofFilename,
publicInputsFilename, schema, simulate)
fmt.Printf("Verifier app returned: %v\n", result.ReturnValue)
}
Verifier app returned: true
Life is sweet :)
The generated smart contract verifiers are ARC4 contracts with the following ABI methods:
create
is used to create the application and will set two global properties:app_name
with the provided nameimmutable
withFalse
@abimethod(create='require')
def create(self, name: String) -> None:
update
allows the creator to update / delete the application unless theimmutable
property has been set toTrue
@abimethod(allow_actions=["UpdateApplication", "DeleteApplication"])
def update(self) -> None:
make_immutable
allows the creator to set theimmutable
property toTrue
, making the contract fully decentralized with no one able to further modify or delete it.
@abimethod
def make_immutable(self) -> None:
verify
takes as parameters a proof and public inputs as exported by AlgoPlonk and returnsTrue
if the proof is verifier,False
otherwise
@abimethod
def verify(self, proof: ..., public_inputs: ...) -> arc4.Bool:
Go unleash the power of zero knowledge proofs on Algorand!
Let us now what you create so that we can curate a list of zk applications.
All release tags are signed by the GPG key 81E0FB63130466B782D4859D6C036245DBDB025D