# Submission and verification

Once you have registered your application's verifying key(s) you are ready to submit application proofs to NEBRA UPA. In this section you will learn how to submit and verify proofs using our SDK.

## Steps to submit and verify proofs

Proofs are submitted to UPA on-chain by calling the `submit` function in the NEBRA UPA contract.

```solidity
function submit(
        bytes32[] calldata circuitIds,
        Groth16CompressedProof[] calldata proofs,
        uint256[][] calldata publicInputs
    ) external payable returns (bytes32 submissionId);
```

Each submission can contain one or more proofs. For convenience and type-safety, we recommend that you use our SDK to submit proofs instead of calling this function directly.

### Step 1: Export proof data

#### SnarkJS

Let `proofData` be the output of snarkjs' `fullProve` function, i.e.

```typescript
const proofData = await snarkjs.groth16.fullProve(
    inputs,
    circuitWasm,
    circuitZkey
  );
```

You may save the json serialization of `proofData` into a file `snarkjs_proof.json` if you intend to submit the proof with the `upa` tool. You can generate a UPA-compatible proof data file with the following command

```bash
upa convert proof-snarkjs \
    --snarkjs-proof snarkjs_proof.json \
    --proof-file proof.upa.json
```

Alternatively, if you want to submit via the typescript sdk, you can easily extract the UPA-compatible proof and inputs from `proofData`:

```typescript
import { Groth16Proof } from "@nebrazkp/upa/sdk";

const proof = Groth16Proof.from_snarkjs(proofData.proof);
const inputs: bigint[] = proofData.publicSignals.map(BigInt);
```

#### Gnark

You can modify your gnark circuit code to export the proof and the inputs as follows:

```go
import ("encoding/json")

proof, _ := groth16.prove(ccs, pk, witness, opt.proverOpts...)
proofJSON, _ := json.MarshalIndent(vk, "", "    ")
_ = os.WriteFile("gnark_proof.json", proofJSON, 0644)

pubWitness, _ := witness.Public()
publicWitnessJSON, _ := json.Marshal(pubWitness)
_ = os.WriteFile("gnark_inputs.json", publicWitnessJSON, 0644)
```

If you intend to submit the proof with the `upa` tool, you may generate a UPA-compatible proof data file with the following command

```bash
upa convert proof-gnark \
    --gnark-proof gnark_proof.json \
    --gnark-inputs gnark_inputs.json \
    --proof-file proof.upa.json
```

Alternatively, if you want to submit via the typescript sdk, you can convert the gnark proofs and inputs to the UPA-compatible format as follows

```typescript
import { Groth16Proof } from "@nebrazkp/upa/sdk";

const proof = Groth16Proof.from_gnark(gnarkProof);
const inputs: bigint[] = gnarkInputs.map(BigInt);
```

where `gnarkProof` and `gnarkInputs` can be obtained from the `gnark_proof.json` and `gnark_inputs.json` files, respectively. For example

```typescript
import type GnarkProof from "@nebrazkp/upa/sdk";
import type GnarkInputs from "@nebrazkp/upa/sdk";

const gnarkProof = JSON.parse(
    fs.readFileSync("path/to/gnark_proof.json", "ascii")
  ) as GnarkProof;
const gnarkInputs = JSON.parse(
    fs.readFileSync("path/to/gnark_inputs.json", "ascii")
  ).map(BigInt) as GnarkInputs;
```

#### Note on gnark proofs

For gnark proofs with a Pedersen commitment point, the UPA only supports those which have been generated with `keccak256` as the hash to field function. In other words, you must run the prover with the following options:

```go
import("golang.org/x/crypto/sha3")

groth16.prove(..., backend.WithProverHashToFieldFunction(sha3.NewLegacyKeccak256()))
```

Note `gnark`'s default is the hash function RFC9380, which is not currently supported by NEBRA's UPA.

### Step 2: Prepare proof data

Each proof is submitted along with its corresponding Circuit Id and public inputs as a `CircuitIdProofsAndInputs`, defined as the following type in the `application` module of the sdk.

```typescript
type CircuitIdProofAndInputs = {
    circuitId: bigint;
    proof: Proof;
    inputs: BigNumberish[];
};
```

Prepare an array `CircuitIdProofsAndInputs[]` of the proofs you will submit.

Alternatively, proofs may be submitted alongside a verifying key instead of a circuit Id, as an array of `AppVkProofInputs`:

```typescript
export class AppVkProofInputs<VK = Groth16VerifyingKey, PROOF = Groth16Proof> {
  constructor(
    public readonly vk: VK,
    public readonly proof: PROOF,
    public readonly inputs: bigint[]
  ) {}
...
}
```

### Step 3: Submit proofs

### Option A: Off-chain submission

Before sending an off-chain submission, you need to deposit ether to the off-chain aggregator's deposit contract. You can make a deposit using the command

```
upa off-chain deposit --deposits-contract <CONTRACT_ADDRESS> --amount-eth <AMOUNT_ETH>
```

\
Then prepare a JSON file containing an array of `AppVkProofInputs` objects. Note that for off-chain submissions, you must submit a verifying key along with the proofs, not a circuit Id.

To submit a proof(s) file named `proof.upa.json`, run the following command

```
upa off-chain submit proof.upa.json --submission-endpoint <SUBMISISION_ENDPOINT> --deposits-contract <CONTRACT_ADDRESS> 
```

If the aggregator agrees to aggregate your submission, then this command outputs a signed response from the aggregator, which you may use to refund the aggregation fee if your submission has not been aggregated by a certain expiration block. (See the command `upa off-chain refund-fee`)

Otherwise, the aggregator rejects your submission then it will respond with an error message.

### Option B: On-chain submission

Using your `UpaClient` [(see setup)](https://docs.nebra.one/developer-guide/setup), submit your array `CircuitIdProofsAndInputs[]`.

```typescript
const submissionHandle = await upaClient.submitProofs(circuitIdProofAndInputs);
```

Be sure to keep the returned `submissionHandle` as it contains information used by your application contract to check whether the proof has been verified by NEBRA UPA.  It contains a `Submission` object that stores the [*proof Ids*](#what-is-a-proof-id) for each submitted proof and a *submission Id* for the entire submission. See [Single and multi-proof submissions](#single-and-multi-proof-submissions) for more details.

#### Fee estimation (optional)

NEBRA UPA charges a nominal fee for each proof submission. Your `UpaClient` can estimate this fee.

```typescript
const value = await upaClient.estimateFee(submissionSize);
```

This fee amount `value` can then be passed as a `PayableOverrides` option into `upaClient.submitProofs`. If no `value` is specified then the fee is computed automatically.

```typescript
const submissionHandle = await upaClient.submitProofs(
  circuitIdProofAndInputs,
  { value }
  );
```

#### Proof submission via the `upa` tool&#x20;

If you have a json file with UPA-compatible proof data such as `proof.upa.json` generated in the previous step.

First, you need to create a file with the circuitId, the proof and the inputs. You can do that e.g. using `jq`:

```bash
jq '. | {circuitId: ${cid}, proof: .proof, inputs: .inputs}' proof.upa.json > cid_proof.upa.json
```

where `cid` is the actual value of the circuit Id computed at vk registration time.

To submit the proof(s), run the following command

```bash
upa submit-proofs --proofs-file cid_proof.upa.json --proof-ids-file proof-id.json --submission-file submission-data.json
```

The option `--proof-ids-file` produces an output file with the proof id(s). In the case of multi-proof submissions, the option`--submission-file` saves the submission data to a file.

If more convenient, it is also possible to submit a file with the verifying key (instead of the circuitId), the proof and the inputs. You can generate such a file with the following command:

```bash
jq --argfile vk vk.upa.json '. | {vk: $vk, proof: .proof, inputs: .inputs}' proof.upa.json > vk_proof.upa.json
```

and then submit the file as before

```bash
upa submit-proofs --proofs-file vk_proof.upa.json --proof-ids-file proof-id.json --submission-file submission-data.json
```

### Step 4: Wait for proofs to be verified on NEBRA UPA

Using your submission's `submissionId`, wait for NEBRA UPA to verify your submission by awaiting `waitForSubmissionVerified`.&#x20;

```typescript
const submitProofTxReceipt = await waitForSubmissionVerified(
  upaInstance,
  submissionId
);
```

Once your submission has been verified, you can send a request to your application contract with inputs corresponding to your submission. This request uses the same inputs as before, but you will no longer need to pass in a proof when using NEBRA UPA. Your application contract will use NEBRA UPA to check the verification status of these inputs before executing the request.

### Step 5: Application contract checks verification status

Your app smart contract will call `isProofVerified` from the NEBRA UPA contracts to check whether a proof has been verified or not.

```solidity
// For single-proof submissions
function isProofVerified(
    bytes32 circuitId,
    uint256[] calldata publicInputs
) external view returns (bool);

// Verify a single proof in a multi-proof 
// submission
function isProofVerified(
    bytes32 circuitId,
    uint256[] calldata publicInputs,
    ProofReference calldata proofReference
) external view returns (bool);

```

\
For single-proof submissions, your smart contract calls `isProofVerified` as follows.

<pre class="language-solidity"><code class="lang-solidity"><strong>// `upaVerifier` is an instance of the `IUpaVerifier` contract interface
</strong><strong>bool isProofVerified = upaVerifier.isProofVerified(circuitId, publicInputs);
</strong></code></pre>

For multi-proof submissions, your application contract will also need to provide a `ProofReference` to identify a specific proof in the submission [(see Proof references)](#multi-submissions)[.](#proof-references)

```solidity
bool isProofVerified = upaVerifier.isProofVerified(
  circuitId,
  publicInputs,
  proofReference
);
```

If you used our typescript SDK for a multi-proof submission, your `SubmissionHandle` can compute this proof reference which can then be passed to your application contract as part of your request.

```typescript
// Gets the proof reference of the j-th proof in this submission.
const proofReference = submissionHandle.submission.computeProofReference(j);
```

## What is a Proof Id?

UPA assigns a $$\mathsf{proofId}$$ to each proof it receives. This $$\mathsf{proofId}$$ is calculated as the Keccak hash of the proof's circuit id and public inputs:

$$
\mathsf{proofId} = \mathsf{keccak}(\mathsf{circuitId}, \mathsf{PI})
$$

### Verifying a Proof Id directly

The `IUpaVerifier` interface contract also provides the following variations of `isProofVerified`:

```solidity
function isProofVerified(bytes32 proofId) external view returns (bool);

function isProofVerified(
    bytes32 proofId,
    ProofReference calldata proofReference
) external view returns (bool);
```

In some cases, computing the proof Id internally in the application contract and calling `isProofVerified` with that proof Id instead of the circuit Id and the public inputs will translate into further gas savings. In that case, we provide the following library function to call from your application contract internally:

```solidity
// [...]
import "@nebrazkp/upa/contracts/UpaLib.sol";

contract YourApp is Groth16Verifier {
    // [...]
    function submitTransaction(
        uint256[] calldata publicInputs,
    ) public {
        bytes32 proofId = UpaLib.computeProofId(circuitId, publicInputs);
        bool isProofCorrect = upaVerifier.isVerified(proofId);
        require(isProofCorrect, "Proof was not correct.");

        // Proceed with app's business logic 
        // ...
    }
}
```

We recommend application developers to always measure the gas costs of both variants before choosing an implementation.

## Single and multi-proof submissions

The majority of the cost of single-proof submissions comes from storing metadata about each proof such as its $$\mathsf{proofId}$$. This storage cost may be [significantly reduced](https://docs.nebra.one/developer-guide/gas-costs-on-l1s) by taking advantage of *multi-proof submissions*.

* Multi-proof submissions store their corresponding $$\mathsf{proofId}$$s in a Merkle tree.
  * The $$\mathsf{submissionId}$$ of a multi-proof submission is the Merkle root of the $$\mathsf{proofId}$$s.
  * A single-proof submission's $$\mathsf{submissionId}$$ is the $$\mathsf{proofId}$$ of its single proof.
* A submission's proofs are either all accepted if all of them are valid, or they are all rejected if any proof is invalid.
* An aggregated batch can contain proofs from different submissions.
* A submission may span multiple batches.

#### Proof references

To check the verification status of the `j`-th proof of a multi-proof submission identified by $$\mathsf{submissionId}$$, you must provide its `ProofReference` in addition to its $$\mathsf{proofId}$$. A `ProofReference`  is a Merkle proof that this $$\mathsf{proofId}$$ is indeed the `j`-th leaf of a Merkle tree with root $$\mathsf{submissionId}$$.

#### Atomic verification of multi-proof submissions

In the case of multi-proof submissions, you can save even more gas at verification time by calling `isSubmissionVerified` instead of repeated calls to `isProofVerified`. The `IUpaVerifier` interfaces provides the following functions

```solidity
// General case
function isSubmissionVerified(
    bytes32[] calldata circuitIds,
    uint256[][] memory publicInputsArray
) external view returns (bool);

// Special case when all circuit Ids are the same
function isSubmissionVerified(
    bytes32 circuitId,
    uint256[][] memory publicInputsArray
) external view returns (bool)
```

which attests to whether every proof in a submission has been verified or not.&#x20;

As with `isProofVerified`, we provide the following variant which only takes a submission Id

```solidity
function isSubmissionVerified(
    bytes32 submissionId
) external view returns (bool);
```

and the following `UpaLib` library functions to compute the submission Id in your application contract:

```solidity
// general case
function computeSubmissionId(
        bytes32[] calldata circuitIds,
        uint256[][] memory publicInputsArray
) internal pure returns (bytes32)

// special case, all circuit ids are the same
function computeSubmissionId(
        bytes32 circuitId,
        uint256[][] memory publicInputsArray
) internal pure returns (bytes32)

// general case, the caller knows the proof Ids
// in the submission
function computeSubmissionId(
        bytes32[] memory proofIds
) internal pure returns (bytes32)
```

<br>
