Registering applications
Before the UPA can aggregate proofs for an application, it must know about the application's verifying key(s).
The UPA contracts expose a registerVK
method, which accepts the verifying key to be used, stores it on-chain (for use during censorship claims), and emits an event informing aggregators of the key data.
The guide here assumes that you have correctly installed the development environment, and have a upa.instance
file pointing to a Saturn deployment. See the setup guide for details.
Converting to UPA-compatible format
Verification keys must be in the UPA-compatible format before registration.
SnarkJS
Exporting the verifying key
This may be done with a command of the form:
Converting the verifying key via the upa
tool
upa
toolThe key retrieved above can be converted to the UPA format as follows:
Converting the verifying key via the typescript sdk
The UPA sdk supports conversion from snarjks zkey
s to the UPA-compatible Groth16VerifyingKey
:
Gnark
Exporting the verifying key
You can modify your circuit compilation code to save the verifying key to a file:
where ccs
is the gnark
ConstraintSystem
of your circuit.
Note: Currently, the NEBRA UPA only supports gnark
configurations for which:
The circuit has zero or one Pedersen commitment points, that is, the field
Commitments
of your proofs is a vector of length 0 or 1.Public inputs are not committed into the commitment point. That is, the
PublicAndCommitmentCommitted
array in the verifying key must be empty.
Converting the verifying key via the upa tool
The key retrieved above can be converted to the UPA format as follows:
If the verifying key belongs to a circuit which uses a Pedersen commitment, you must add the flag
to the command above. Your circuit uses a Pedersen commitment if the field Commitments
of your proof is a vector of length 1.
Converting the verifying key via the typescript sdk
where vkGnark
can be obtained by parsing the gnark_vk.json
file extracted above. For example:
Registering via the upa
tool
upa
toolOnce you have the file app_vk.upa.json
with the UPA-compatible verifying key, you can register it with the following command:
The circuit Id for this key is then output to stdout. Record this for use in your application. It can be recomputed via
Registering from Typescript
(See the setup guide for instructions on creating a UpaClient
)
Let vk
be the variable holding a Groth16VerifyingKey
, generated e.g. from snarkjs or gnark with the UPA sdk. You can easily register the verifying key with the UpaClient
:
The circuit Id can also be computed from the VerifyingKey
from the typescript sdk.
What is Circuit Id?
Circuit Id is a unique identifier, assigned to each circuit when its verifying key is registered with the UPA contracts. It is computed (deterministically) as the keccak hash of the verifying key contents, with a domain tag. The UPA contract store a map from Circuit Id to Verifying Key data, for use during censorship claims. When submitting proofs to UPA, applications specify the Circuit Id for each proof being submitted.
Circuit Ids are also used to compute the unique Proof Id for each proof submitted to the UPA (where Proof Id is the Keccak digest of the Circuit Id followed by the public inputs).
Aggregators use the Circuit Id to look up the corresponding Verifying Key to be used as witness data in the aggregation proof. The aggregation proof then attests to the set of Proof Ids that appear in the aggregation. When application contracts query UPA to determine the validity of a given proof, the Proof Id is computed and used to check whether a batch including that proof has been verified.
NOTE: The Circuit Id is required when proofs are submitted to NEBRA UPA.
See upa compute circuit-id --help
.
A note about G2 formats
Most developers will not have to deal with the details of this, but it can be helpful to be aware of the following potential pitfall.
Some attributes of the Groth16 Verifying Key are elements of the so-called G2
curve group. The details can be found elsewhere (e.g. in the upa-sdk
reference documentation), but it is important to be aware that there are two incompatible formats for these points. Most off-chain libraries, including snarkjs
, use a natural ordering of coordinates in G2
, while the EVM expects them to be reversed.
The UPA SDK and contracts work as follows:
All sdk functions and types use the natural ordering, including
Groth16VerifyingKey
,Groth16Proof
etc, compatible with snarkjs. These types generally expose a static constructor such asGroth16VerifyingKey.from_snarkjs()
that accepts the snarkjs version, and a methodsolidity()
which returns the data as expected by the NEBRA contract.Conversion should generally be handled automatically by the UPA SDK and tools, and the SDK uses types where possible to catch any compatibility problems. For reference:
VerifyingKeys are passed to the UPA contracts with G2 coordinates in the natural order, since they are generally only used by off-chain tools. (On-chain verification only happens in the case of censorship claims).
Proofs are passed to the UPA with G2 coordinates in the EVM order. This is because this is the the form in which application generally pass proofs to their contracts. This simplifies the integration of UPA into existing applications, since no conversion is required.
The
solidity()
method onapplication
objects adheres to the convention described above, that is,vk.solidity()
is compatible with the UPA contracts, but may not be compatible with on-chain Groth16 verifiers.
For most applications, the Verifying Key is embedded automatically in a verification contract, and the application does not have to interact with it. The above pitfalls are therefore only expected to be relevant to applications with custom pipelines for their circuits.
Last updated