Sigil — Verifiable Developer Credentials
What Problem Does Sigil Solve?
Traditional developer portfolios and resumes rely heavily on self-reported information and public GitHub activity ("green squares"). This approach is fraught with issues:
- Lack of Verifiability: Recruiters and hiring managers have no way to cryptographically verify that a candidate actually made the contributions they claim, especially in private or NDA-bound repositories.
- Privacy Concerns: Developers may be unable or unwilling to share source code or sensitive project details, especially from private or proprietary work.
- Resume Inflation: Self-reported skills and contributions are easy to exaggerate, leading to trust issues in the hiring process.
- Fragmented Identity: Developers must juggle multiple profiles (GitHub, LinkedIn, resumes) with no cryptographically secure link to their real work.
How Does Sigil Help?
Sigil introduces a Web3-native, privacy-preserving solution for developer credentialing:
- Verifiable Proofs of Work: Developers can generate cryptographic, zero-knowledge proofs (ZKPs) of their GitHub contributions—including from private or NDA-bound repositories—without exposing any source code or sensitive data.
- Wallet-Linked Identity: Proofs are bound to the developer’s Ethereum wallet, creating a tamper-proof, portable identity that can be used across platforms.
- Recruiter Trust Layer: Recruiters and hiring managers can instantly verify the authenticity of a candidate’s contributions via a simple, human-readable certificate and a trustless proof verifier UI.
- Privacy by Design: All proofs are generated in a way that reveals only the necessary metadata (e.g., lines of code, file types, langauges used), never the code itself.
- Seamless Sharing: Developers get a downloadable certificate of verifiable credentials, making it easy to showcase their work.
What Can People Use Sigil For?
For Developers
- Prove Your Experience: Generate and share cryptographic credentials for your real GitHub contributions, even from private or sensitive projects.
- Protect Your Privacy: Demonstrate your skills and work history without exposing proprietary code or violating NDAs.
- Stand Out in Hiring: Replace unverifiable resumes and GitHub green squares with tamper-proof, wallet-linked credentials.
- Build a Portable Portfolio: Maintain a single, verifiable profile that can be used across job applications, freelance platforms, and professional networks.
For Recruiters & Hiring Managers
- Instantly Verify Candidates: Trustlessly confirm that a candidate made the contributions they claim, without needing access to private repos or code.
- Reduce Hiring Risk: Rely on cryptographic proofs rather than self-reported data, reducing the risk of resume inflation or misrepresentation.
- Streamline Evaluation: Quickly assess a candidate’s skills, stack, and real-world impact via human-readable, ZK-backed certificates.
For Platforms & Ecosystems
- Integrate Trust: Platforms like Replit, RemoteOK, or TalentLayer can integrate Sigil to offer verifiable developer credentials, enhancing trust and reputation systems.
- Enable New Workflows: Support new forms of hiring, collaboration, and reputation-building that are privacy-preserving and cryptographically secure.
How Does Sigil Make Existing Tasks Easier or Safer?
- No More Manual Verification: Recruiters no longer need to request code samples or access to private repositories—Sigil’s proofs are trustless and instant.
- Privacy-Preserving by Default: Developers can prove their work without ever exposing sensitive information.
- Tamper-Proof Credentials: All proofs are cryptographically bound to the developer’s wallet, making them impossible to forge or alter.
- Frictionless Sharing: One-click portfolio links and certificates make it easy to share and verify credentials anywhere.
Sigil transforms the way developers prove their experience and how recruiters verify talent—making the hiring process safer, faster, and more trustworthy for everyone involved.
Challenges I Ran Into
1. Contract Authorization Nightmare
The Problem
The biggest hurdle was a seemingly simple error:
UnauthorizedVerifier
. Users couldn't register credentials because the
CredentialRegistry
contract had an
onlyAuthorizedVerifier
modifier that required explicit authorization from the contract owner.
The Journey
- Initial Approach: Tried to work around it by implementing auto-authorization mechanisms
- Failed Attempts: Attempted to use the
SigilCredentialVerifier
contract as a workaround
- Complex Debugging: Spent hours parsing viem contract errors and implementing simulation checks
- The Lightbulb Moment: Realized the simplest solution was to fix the root cause
The Solution
Instead of complex workarounds, I modified the contract itself:
- Removed the
onlyAuthorizedVerifier
modifier from the registerCredential
function
- Redeployed all contracts to Sepolia with the authorization requirement removed
- Updated frontend contract addresses
Lesson Learned: Sometimes the best solution is the simplest one - fix the root cause rather than building elaborate workarounds.
2. Real vs Mock Proof Integration
The Problem
The system was using mock proofs instead of actual ZK proof generation, making it more of a demo than a real implementation.
The Technical Challenge
- Circuit Compilation Issues: Many ZK circuits had invalid or empty
.zkey
files
- Trusted Setup Problems: The
repository_credential
circuit required 772,125*2 constraints but the Powers of Tau file only supported 2^16 (65,536)
- Performance Issues: Real ZK proof generation took 84+ seconds per proof
The Solution
- Circuit Analysis: Discovered 18 working circuits and identified which ones had valid trusted setups
- Fallback Strategy: Used the simpler
hash_chain
circuit for demo purposes
- API Integration: Built
/api/snark/generate
endpoint using actual snarkjs library
- Caching System: Implemented proof caching to avoid regenerating identical proofs
- Timeout Protection: Added 2-minute timeouts to prevent hanging requests
Lesson Learned: ZK proof systems are complex - having fallback strategies and proper error handling is crucial for production systems.
3. Chain Mismatch Hell
The Problem
Users kept getting
ContractFunctionExecutionError: The current chain of the wallet (id: 84532) does not match the target chain for the transaction (id: 11155111 – Sepolia)
.
The Debugging Process
- Multiple Issues: Users had wallets connected to Base network instead of Sepolia
- Missing Parameters: Contract calls were missing
account
and chain
parameters
- Type Conversion Errors: BigInt conversion errors with decimal values like "86.09"
The Solution
- Network Detection: Built
NetworkSwitcher
component to detect and warn about wrong networks
- Auto-Switching: Implemented
ensureSepoliaNetwork()
method for automatic network switching
- Parameter Fixes: Added explicit
account: userAddress
and chain: sepolia
to all contract calls
- Data Validation: Added
Math.floor()
before BigInt conversions
Lesson Learned: Multi-chain applications need robust network management and clear user guidance.
4. JSON Upload Format Chaos
The Problem
Users couldn't upload proof JSON files because the system only supported one specific format, but the proof generation created a different format.
The Format Confusion
The proof generation created full Verifiable Credentials:
{
"proof": {
"proofValue": { "a": [...], "b": [...], "c": [...] },
"publicSignals": [...],
"credentialType": "aggregate"
},
"credentialSubject": {...}
}
But the verification expected simple proof objects.
The Solution
Built a universal JSON parser that handles multiple formats:
- Full Verifiable Credential format
- Raw proof format from credentials
- Simple ZKProofData format
- Standard snarkjs format
- Groth16 format with
pi_a
, pi_b
, pi_c
Lesson Learned: When building developer tools, support multiple input formats to improve user experience.
5. React Hooks Dependency Array Mayhem
The Problem
Getting warnings about useEffect dependency arrays changing size between renders, causing infinite re-renders.
The Root Cause
useEffect(() => {
// Set demo data
}, [credentialHash, proof, publicSignals]); // These values change!
The Solution
Realized this should only run once on mount:
useEffect(() => {
// Set demo data - only run once on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Lesson Learned: Understanding when effects should run is crucial - not every effect needs reactive dependencies.
6. TypeScript Warning Avalanche
The Problem
Build was successful but had 20+ TypeScript warnings about unused variables,
any
types, and missin