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:
Credential Storage: Users can add credentials signed by authorized institutions
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
Key Management: Securely store institution private keys
Data Privacy: Never log or expose raw credential data
Access Control: Implement proper verification for institution authorization
Circuit Complexity: Optimize proof circuits to maintain performance
Best Practices
Use Protokit's built-in testing utilities extensively
Keep zero-knowledge circuits as simple as possible
Implement proper error handling for all operations
Follow the principle of minimal disclosure
Resources for Further Learning
Troubleshooting Common Issues
Proof Generation Failures
Ensure all circuit constraints are properly defined
Check for undefined or null values in inputs
Deployment Issues
Verify network configuration
Ensure sufficient MINA balance for deployment
Performance Problems
Optimize circuit constraints
Reduce the number of state variables
Use batch processing where appropriate