> ## Documentation Index
> Fetch the complete documentation index at: https://docs.galxe.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Proof Generation & Verification Workflow

> Hands-On Zero-Knowledge Proofs with Galxe Identity Protocol

## Introduction

This tutorial guides you through the process of issuing and verifying credentials using Galxe Identity Protocol on Ethereum. You will learn how to create and sign a credential, generate a zero-knowledge proof, and perform both on-chain and off-chain verifications.

## Prerequisites

* Basic understanding of Ethereum and smart contracts.
* Node.js installed on your machine.
* An IDE or text editor for writing JavaScript code.

## Step 1: Setup and initialize Nodejs repo

In your Node.js repo, run:

```bash theme={null}
npm install @galxe-identity-protocol/sdk ethers
```

## Step 2: Import necessary dependencies and setup issuer

<Warning> Do not use the dummy issuer in production. </Warning>

Note that the dummy issuer has been registered on Ethereum mainnet. Check out the registration transaction [here](https://etherscan.io/tx/0xfbf58513352fe0dc899544b66013adbacb90437210e0008ceeb7b9c9ecec5bdd).

This issuer is registered with the following parameters:

| parameter           | type    | input                                                                                                                              |
| ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| name                | string  | demo-issuer (don't trust me)                                                                                                       |
| verificationStackId | uint8   | 1                                                                                                                                  |
| publicKeyId         | uint256 | 1743582416365651167392966598529843347617363862106697818328310770809664607117                                                       |
| publicKeyRaw        | bytes   | 0x1adaddae1888a2b6e91240896f5e50379520137f76c424755217ade00f8b2d185a05a904d745ee24692d91cf74425d4271817e64e630115469f3f289d0d06b2f |

* `verificationStackId = 1` means that this issuer is registered for babyzk stack

```typescript theme={null}
import {
  prepare,
  credential,
  evm,
  credType,
  errors,
  user,
  issuer,
  babyzk,
  utils,
  statement,
  claimType,
  babyzkTypes,
} from "@galxe-identity-protocol/sdk";
import { ethers } from "ethers";

// conviniently unwrap the result of a function call by throwing an error if the result is an error.
const unwrap = errors.unwrap;

// Use cloudflare's free open rpc in this example.
const MAINNET_RPC = "https://cloudflare-eth.com";
const provider = new ethers.JsonRpcProvider(MAINNET_RPC);

// This is a dummy issuer's EVM address that has been registered on mainnet.
// Because it authroize the private key that is public to everyone,
// it should not be used in production!
const dummyIssuerEvmAddr = "0x15f4a32c40152a0f48E61B7aed455702D1Ea725e";
```

## Step 3: Issue a new credential

Note that this context has been registered on Ethereum mainnet. Check out the registration transaction [here](https://etherscan.io/tx/0xfec5eea6c7781db45265038a64023ee74d002e240978a6483a3291177b9c63e8).

```typescript theme={null}
async function issuingProcess(userEvmAddr: string, userIdc: bigint) {
  // 1. First of all, we must create the type of the credential.
  // In this example, Let's use the primitive type Scalar.
  const typeSpec = credType.primitiveTypes.scalar;
  const tp = unwrap(credType.createTypeFromSpec(typeSpec));

  // 2. Creating a credential based on the type.
  // In general, this is when the issuer decides "claims" about the user.
  // Because we are issuing a credential that represents the number of transactions,
  // let's fetch it from the Ethereum network.
  const txCount = await provider.getTransactionCount(userEvmAddr);
  // The contextID is a unique identifier representing the context of the credential.
  // We will just use the string "Number of transactions".
  // NOTE: The contextID must be registered on the chain before issuing the credential for visibility.
  const contextID = credential.computeContextID("Number of transactions");
  // Now, let's create the credential.
  const newCred = unwrap(
    credential.Credential.create(
      {
        type: tp,
        contextID: contextID,
        userID: BigInt(userEvmAddr),
      },
      {
        val: BigInt(txCount).toString(), // credential value, number of transactions
      }
    )
  );
  // Add additional attributes to the credential attachments, if needed
  // these attributes will not be part of the zero-knowledge proof, but
  // they will be signed by the issuer as well.
  // So, you must add them before signing the credential.
  newCred.attachments["creativity"] = "uncountable";

  // 3. Signing the credential.
  // After the credential is created, it must be signed by the issuer.
  // The issuer must have been registered on the chain, at least on the chain of the supplied ChainID.
  // Registering the issuer on more chains is recommended for better interoperability.
  // Also, the signing key's keyID must be active correspondingly on chains.
  // For demonstration purposes, we use the dummy issuer with a publicly known key.
  // The dummy issuer has been registered on etheruem mainnet, and the following key is also activated.
  // Don't use this issuer or key in production!
  const issuerID = BigInt(dummyIssuerEvmAddr);
  const issuerChainID = BigInt(1); // mainnet
  // A mock private key for the signer, which is used to sign the credential.
  // This key has been registered and activated on mainnet by the dummy issuer.
  const dummyKey = utils.decodeFromHex(
    "0xfd60ceb442aca7f74d2e56c1f0e93507798e8a6e02c4cd1a5585a36167fa7b03"
  );
  const issuerPk = dummyKey;
  // create a new issuer object using the private key, issuerID, and issuerChainID.
  const myIssuer = new issuer.BabyzkIssuer(issuerPk, issuerID, issuerChainID);
  // sign the credential to user's identity commitment, with a unique signature id and expiration date.
  myIssuer.sign(newCred, {
    sigID: BigInt(100),
    expiredAt: BigInt(
      Math.ceil(new Date().getTime() / 1000) + 7 * 24 * 60 * 60
    ), // assuming the credential will be expired after 7 days
    identityCommitment: userIdc,
  });

  // all done, return the credential to the owner.
  return newCred;
}
```

## Step 4: Proof generation

```typescript theme={null}
async function proofGenProcess(myCred: credential.Credential, u: user.User) {
  // Now issuer can issue a credential to the user.
  // In this example, we will issue a credential that represents the number of transactions,
  // that the user has made on the Ethereum, at the time of issuance.
  // Assuming that the user has received the credential,
  // user can generate a zk proof to prove that he has sent more than 500 transactions, but no more than 5000.
  // Let's first decide the external nullifier for the proof.
  const externalNullifier = utils.computeExternalNullifier(
    "Galxe Identity Protocol tutorial's verification"
  );
  // Now we need to fetch the proof generation gadgets. It is explicitly fetched outside the proof generation function
  // because usually, the proof generation gadgets are stored in a remote server, and may be large (3-10MB).
  // It's highly recommended to cache the proof generation gadgets locally.
  console.log("downloading proof generation gadgets...");
  const proofGenGagets = await user.User.fetchProofGenGadgetsByTypeID(
    myCred.header.type,
    provider
  );
  console.log("proof generation gadgets are downloaded successfully.");
  // Finally, let's generate the proof.
  const proof = await u.genBabyzkProof(
    u.getIdentityCommitment("evm")!,
    myCred,
    // proof generation options
    {
      expiratedAtLowerBound: BigInt(
        Math.ceil(new Date().getTime() / 1000) + 3 * 24 * 60 * 60
      ), // assume that we want to verify that the credential is still valid after 3 days.
      externalNullifier: externalNullifier,
      equalCheckId: BigInt(0), // do not reveal the credential's actual id, which is the evm address in this example
      // Instead, claim to be Mr.Deadbeef. It's verifier's responsibility to verify that
      // the pseudonym is who he claims to be, after verifying the proof.
      pseudonym: BigInt("0xdeadbeef"),
    },
    proofGenGagets,
    // statements to be proved, in this case, we want to prove that the credential's first uint248 value is between 500 and 5000, inclusively.
    [new statement.ScalarStatement(new claimType.ScalarType(248), 500n, 5000n)]
  );
  return proof;
}
```

## Step 5: Proof verification

Proof verification can be made both on-chain and off-chain. Here we demonstrates both options:

### On-chain verification

```typescript theme={null}
async function verifyByCallingEvmStatefulVerifier(
  proof: babyzkTypes.WholeProof
): Promise<boolean> {
  // As a verifier, we must decide the expected
  // contextID, issuerID, and typeID of the credential.
  const expectedContextID = credential.computeContextID(
    "Number of transactions"
  );
  const expectedIssuerID = BigInt(dummyIssuerEvmAddr);
  const expectedTypeID = credType.primitiveTypes.scalar.type_id;

  // A proof can be verified both on-chain or off-chain.

  // Let's take a look at the on-chain verification first.
  // It is just 1 simple function call.
  const statefulVerifier = evm.v1.createBabyzkStatefulVerifier({
    signerOrProvider: provider,
  });
  const statefulVerifierResult = await statefulVerifier.verifyWholeProofFull(
    expectedTypeID,
    expectedContextID,
    expectedIssuerID,
    proof
  );
  if (statefulVerifierResult !== evm.VerifyResult.OK) {
    console.error(
      "Proof verification failed, reason: ",
      evm.verifyResultToString(statefulVerifierResult)
    );
  } else {
    console.log("On-chain stateful proof verification is successful.");
  }
  return true;
}
```

### Off-chain verification

```typescript theme={null}
async function verifyByOffchain(
  proof: babyzkTypes.WholeProof
): Promise<boolean> {
  // As a verifier, we must decide the expected
  // contextID, issuerID, and typeID of the credential.
  const expectedContextID = credential.computeContextID(
    "Number of transactions"
  );
  const expectedIssuerID = BigInt(dummyIssuerEvmAddr);
  const expectedTypeID = credType.primitiveTypes.scalar.type_id;

  // Alternatively, you can use the off-chain verification.
  // When using off-chain verification, you must first get the verification key.
  // You can embed the verification key in your application, or fetch it from a remote server.
  // We will fetch the verification key from the chain in this example.
  // The first step is to do a static proof verification, making sure that the zk proof is valid.
  const tpRegistry = evm.v1.createTypeRegistry({
    signerOrProvider: provider,
  });
  const verifier = await tpRegistry.getVerifier(
    expectedTypeID,
    credential.VerificationStackEnum.BabyZK
  );
  const vKey = await verifier.getVerificationKeysRaw();
  // off-chain proof verification
  const verifyResult = await babyzk.verifyProofRaw(vKey, proof);
  console.log("off-chain static proof verification result: ", verifyResult);
  // NOTE: you can also do the static proof verification part on-chain
  // See the commented code below.
  // const onChainVerifyResult = await verifier.verifyWholeProof(proof);
  // console.log(
  //   "on-chain static proof verification result: ",
  //   onChainVerifyResult
  // );
  // After the static proof verification is successful, you will need to do
  // some addition on-chain stateful checks and off-chain checks.
  // Check If the public key is active.
  const IssuerRegistry = evm.v1.createIssuerRegistry({
    signerOrProvider: provider,
  });
  const pubkeyId = babyzk.defaultPublicSignalGetter(
    credential.IntrinsicPublicSignal.KeyId,
    proof
  );
  if (pubkeyId === undefined) {
    return false;
  }
  // check on-chain if the public key is active by the issuer.
  const isActive = await IssuerRegistry.isPublicKeyActiveForStack(
    expectedIssuerID,
    pubkeyId,
    credential.VerificationStackEnum.BabyZK
  );
  if (!isActive) {
    console.error("The public key is not active.");
    return false;
  }
  // some off-chain checks
  const contextId = babyzk.defaultPublicSignalGetter(
    credential.IntrinsicPublicSignal.Context,
    proof
  );
  if (contextId === undefined) {
    console.error("Context ID is not found in the proof.");
    return false;
  }
  if (contextId !== expectedContextID) {
    console.error(
      `Context ID is not as expected: ${contextId}, expected: ${expectedContextID}`,
      `${typeof contextId}, ${typeof expectedContextID}`
    );
    return false;
  }
  const expiredAtLb = babyzk.defaultPublicSignalGetter(
    credential.IntrinsicPublicSignal.ExpirationLb,
    proof
  );
  if (expiredAtLb === undefined) {
    console.error("Expiration time is not found in the proof.");
    return false;
  }
  if (expiredAtLb < BigInt(Math.ceil(new Date().getTime() / 1000))) {
    console.error("The credential is expired.");
    return false;
  }
  // NOTE: you will need to check if the credential is revoked or not, if the credential type supports revocation.
  // We will not cover the revocation in this example, because we used a primitive type that does not support revocation.
  console.log("Off-chain verification is successful.");
  return true;
}
```

## Step 6: Put it all together

```typescript theme={null}
async function main() {
  // prepare must be called by the application before any other function.
  await prepare();

  // The very first step is to create a user with a random identity.
  // This should be done on user's device and the identity should be stored securely.
  const u = new user.User();
  const evmIdSlice = u.createNewIdentitySlice("evm");

  // User's identity commitment is computed based on the secrets of the identity slice.
  // You can also retrive the identity commitment from the identity slice.
  const userIdc = user.User.computeIdentityCommitment(evmIdSlice);

  // let's use a famous Ethereum address in this example.
  const userEvmAddr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  // Issuer's process: issuing a credential to the user.
  const myCred = await issuingProcess(userEvmAddr, userIdc);
  console.log("Credential is issued successfully.");
  console.log(myCred.marshal(2));

  // User's process: generating a zk proof to prove some statements about the credential.
  const proof = await proofGenProcess(myCred, u);
  console.log("Proof is generated successfully.", proof);

  // Verification process: verifying the proof.
  await verifyByCallingEvmStatefulVerifier(proof);
  // Alternatively, you can verify the proof off-chain.
  await verifyByOffchain(proof);

  process.exit(0);
}

main();
```

## Example output

```
Credential is issued successfully.
{
  "header": {
    "version": "1",
    "type": "3",
    "context": "76531616260669148123754708449894501309630588037",
    "id": "1238012972454248237435767387143779415173800484933"
  },
  "body": {
    "val": "1230"
  },
  "signatures": [
    {
      "metadata": {
        "verification_stack": 1,
        "signature_id": "100",
        "expired_at": "1713809756",
        "identity_commitment": "607258966955358541979569552241800390247193769644933770458665842211944003189",
        "issuer_id": "125344402375953606533377270523694284815265854046",
        "chain_id": "1",
        "public_key": "GtrdrhiIorbpEkCJb15QN5UgE392xCR1Uhet4A+LLRhaBakE10XuJGktkc90Ql1CcYF+ZOYwEVRp8/KJ0NBrLw=="
      },
      "signature": "JoCOG4g8Zh6HbRx6CiB8iT+IFRv7rODqkKOun/ZRPK+AXczTT/naAKDFPLmeSi9vJNmyivXJOTI0rrFZqqtPBA==",
      "attachmentsSignature": "5BJbz+KoS3fNM/nIEHZ8dJNnNoI7Sfj9dyVuITPqOxu+7WDqQPZi8hRYWnWHbKwu7WyzaX/FwibgS/PHpY0DBA=="
    }
  ],
  "attachments": {
    "creativity": "uncountable"
  }
}
downloading proof generation gadgets...
proof generation gadgets are downloaded successfully.
Proof is generated successfully. {
  proof: {
    pi_a: [
      '14098169155626322701575686270030877135793179179958248476088718329427027211313',
      '10166291938235695325174257585123622443224042363089146982965433209462199866261',
      '1'
    ],
    pi_b: [ [Array], [Array], [Array] ],
    pi_c: [
      '2232690526946879183375289587329787117413207137997669929492279533329962669798',
      '2812892456912632652909338355747715034651129220189926137857946196375602442475',
      '1'
    ],
    protocol: 'groth16',
    curve: 'bn128'
  },
  publicSignals: [
    '3',
    '76531616260669148123754708449894501309630588037',
    '20262325705979633926857839541911120644083373009050920843747535916548539604767',
    '3735928559',
    '515399344354422600182581914538985155404062012635',
    '1713464160',
    '1743582416365651167392966598529843347617363862106697818328310770809664607117',
    '0',
    '500',
    '5000'
  ]
}
On-chain stateful proof verification is successful.
off-chain static proof verification result:  true
Off-chain verification is successful.
```

# Conclusion

Congratulations! You've successfully issued a credential, generated a zero-knowledge proof, and verified it both on-chain and off-chain. This tutorial demonstrates the powerful capabilities of the Galxe Identity Protocol in maintaining privacy and security through zero-knowledge proofs.
