Building a Privacy-Preserving Credential System with Protokit and Mina

Building a Privacy-Preserving Credential System with Protokit and Mina

Protokit is a powerful developer toolkit designed to simplify the creation of zkApps (zero-knowledge applications) on the Mina Protocol. In this tutorial, we'll build a privacy-preserving credential verification system that demonstrates the power of zkApps while maintaining user privacy through zero-knowledge proofs.

What are zkApps?

zkApps are smart contracts that leverage zero-knowledge proofs (specifically zk-SNARKs) to enable privacy-preserving computations on the Mina blockchain. Unlike traditional smart contracts that expose all data publicly, zkApps allow developers to create applications where users can prove statements about their data without revealing the actual data.

Why Protokit?

Protokit abstracts away much of the complexity involved in working with zk-SNARKs, providing developers with a familiar JavaScript-based environment for building privacy-preserving applications. It handles the intricate cryptographic implementations while allowing developers to focus on application logic.

Project Overview: CredentialVerifier

We'll build a CredentialVerifier zkApp that allows:

  • Users to store encrypted educational credentials

  • Institutions to issue verified credentials

  • Employers to verify specific qualifications without seeing the actual credentials

Development Environment Setup

Prerequisites

# Install Node.js (v16 or later recommended)
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs

# Install Mina CLI
npm install -g @o1labs/mina-cli

# Create a new project directory
mkdir credential-verifier
cd credential-verifier

# Initialize a new Protokit project
npm init zkapp@latest

Project Structure

credential-verifier/
├── src/
│   ├── CredentialVerifier.ts    # Main zkApp contract
│   ├── credential.ts            # Credential data structure
│   └── proofs/                  # Zero-knowledge proof circuits
├── test/
│   └── CredentialVerifier.test.ts
└── config/
    └── network.json

Smart Contract Implementation

Let's implement our CredentialVerifier contract:

import {
  SmartContract,
  state,
  State,
  method,
  DeployArgs,
  Permissions,
  PublicKey,
  Signature,
  Field,
} from 'snarkyjs';

class Credential {
  constructor(
    public institution: PublicKey,
    public field: Field,
    public graduationYear: Field,
    public gpa: Field
  ) {}

  // Hash the credential for private storage
  hash(): Field {
    return Field.hash([
      this.institution.toFields(),
      this.field,
      this.graduationYear,
      this.gpa,
    ]);
  }
}

export class CredentialVerifier extends SmartContract {
  // Store credential hashes
  @state(Field) credentialRegistry = State<Field>();

  // Store authorized institutions
  @state(PublicKey) authorizedInstitutions = State<PublicKey>();

  @method addCredential(
    credential: Credential,
    signature: Signature
  ) {
    // Verify institution signature
    signature.verify(
      this.authorizedInstitutions.get(),
      credential.hash().toFields()
    ).assertEquals(true);

    // Store credential hash
    this.credentialRegistry.set(credential.hash());
  }

  @method verifyQualification(
    credential: Credential,
    minimumGPA: Field,
    requiredField: Field
  ) {
    // Verify credential exists
    const storedHash = this.credentialRegistry.get();
    storedHash.assertEquals(credential.hash());

    // Verify requirements
    credential.gpa.assertGreaterThanOrEqual(minimumGPA);
    credential.field.assertEquals(requiredField);
  }
}

Understanding the Code

The contract implements two main functionalities:

  1. Credential Storage: Users can add credentials signed by authorized institutions

  2. Qualification Verification: Employers can verify if a credential meets specific requirements

The use of zk-SNARKs allows us to:

  • Keep the actual credential data private

  • Prove requirements are met without revealing specific values

Testing the Contract

Create a test file to verify the contract's functionality:

import { CredentialVerifier, Credential } from '../src/CredentialVerifier';
import { LocalBlockchain, PrivateKey, PublicKey, Signature } from 'snarkyjs';

describe('CredentialVerifier', () => {
  let localBlockchain: LocalBlockchain;
  let zkApp: CredentialVerifier;
  let institution: PrivateKey;

  beforeAll(async () => {
    localBlockchain = new LocalBlockchain();
    institution = PrivateKey.random();

    zkApp = new CredentialVerifier(
      PublicKey.fromPrivateKey(institution)
    );
    await zkApp.deploy();
  });

  it('should verify credentials correctly', async () => {
    const credential = new Credential(
      institution.toPublicKey(),
      Field(1), // Computer Science
      Field(2023), // Graduation Year
      Field(385) // GPA (3.85)
    );

    const signature = Signature.create(
      institution,
      credential.hash().toFields()
    );

    await zkApp.addCredential(credential, signature);

    // Verify qualification (GPA >= 3.80 in Computer Science)
    await zkApp.verifyQualification(
      credential,
      Field(380),
      Field(1)
    );
  });
});

Deployment

To deploy to the Mina testnet:

# Build the project
npm run build

# Configure network
echo '{
  "networkId": "testnet",
  "url": "https://proxy.testnet.minaprotocol.com/graphql"
}' > config/network.json

# Deploy contract
mina zkapp deploy credential-verifier.js

Security Considerations

  1. Key Management: Securely store institution private keys

  2. Data Privacy: Never log or expose raw credential data

  3. Access Control: Implement proper verification for institution authorization

  4. Circuit Complexity: Optimize proof circuits to maintain performance

Best Practices

  1. Use Protokit's built-in testing utilities extensively

  2. Keep zero-knowledge circuits as simple as possible

  3. Implement proper error handling for all operations

  4. Follow the principle of minimal disclosure

Resources for Further Learning

Troubleshooting Common Issues

  1. Proof Generation Failures

    • Ensure all circuit constraints are properly defined

    • Check for undefined or null values in inputs

  2. Deployment Issues

    • Verify network configuration

    • Ensure sufficient MINA balance for deployment

  3. Performance Problems

    • Optimize circuit constraints

    • Reduce the number of state variables

    • Use batch processing where appropriate