Sphyre Verifier
Sphyre Verifier is the web application designed for organizations to request, receive, and verify credentials from users. It enables privacy-preserving verification with support for selective disclosure and zero-knowledge proofs.Overview
Sphyre Verifier allows organizations to verify user credentials without storing unnecessary personal data, supporting compliance with privacy regulations like GDPR.Technology
Framework: Next.js 14
Language: TypeScript
URL:
Language: TypeScript
URL:
https://verifier.sphyre.techKey Features
Presentation requests
Instant verification
Zero-knowledge proofs
Verification history
Instant verification
Zero-knowledge proofs
Verification history
Key Features
Presentation Requests
Presentation Requests
- Create custom verification requests
- Specify required credentials and claims
- Support for zero-knowledge predicates
- QR code generation for easy scanning
- Expiring request links
Credential Verification
Credential Verification
- Instant cryptographic verification
- Blockchain anchor validation
- Revocation status checking
- Issuer trust verification
- Multi-credential validation
Privacy-Preserving
Privacy-Preserving
- Selective disclosure support
- Zero-knowledge proof verification
- Minimal data collection
- No personal data storage
- GDPR compliant by design
Verification History
Verification History
- Track all verifications
- Export verification records
- Audit trail
- Search and filter
- Compliance reporting
Consent Management
Consent Management
- User consent tracking
- Purpose specification
- Data usage transparency
- Consent revocation handling
Application Structure
Copy
sphyre-verifier/
├── src/
│ ├── app/
│ │ ├── Dashboard/ # Main dashboard
│ │ ├── CreateRequest/ # New verification request
│ │ ├── Presentation/ # View presentations
│ │ ├── Verifications/ # History
│ │ ├── Settings/ # Configuration
│ │ └── TrustRegistry/ # Trusted issuers
│ ├── components/
│ │ ├── RequestBuilder.tsx # Build verification requests
│ │ ├── QRDisplay.tsx # Display QR codes
│ │ ├── PresentationViewer.tsx # View submissions
│ │ ├── VerificationResult.tsx # Show results
│ │ └── TrustBadge.tsx # Issuer trust indicator
│ ├── services/
│ │ ├── apiService.ts # API client
│ │ ├── verificationService.ts # Verification logic
│ │ └── zkpService.ts # ZKP validation
│ └── types/
│ └── verification.ts # TypeScript definitions
└── package.json
Core Workflows
1. Create Presentation Request
Build a verification request specifying what credentials to request. Request Builder Interface:Copy
interface PresentationRequest {
id: string;
verifierId: string;
verifierName: string;
purpose: string;
requiredCredentials: CredentialRequirement[];
optionalCredentials?: CredentialRequirement[];
expiresAt: Date;
challenge: string;
}
interface CredentialRequirement {
schemaId: string;
issuerDids?: string[]; // Trusted issuers
fields?: string[]; // Specific claims needed
predicates?: Predicate[]; // For ZKP
}
interface Predicate {
field: string;
type: 'greaterThan' | 'lessThan' | 'equals' | 'memberOf';
value: any;
}
Copy
const CreateRequest = () => {
const [request, setRequest] = useState({
purpose: '',
requiredCredentials: [],
expiresIn: 3600 // 1 hour
});
const [useZKP, setUseZKP] = useState(false);
const addCredentialRequirement = () => {
setRequest({
...request,
requiredCredentials: [
...request.requiredCredentials,
{ schemaId: '', fields: [] }
]
});
};
const createRequest = async () => {
try {
const response = await apiService.createPresentationRequest({
...request,
verifierId: getVerifierDid(),
verifierName: getVerifierName(),
expiresAt: new Date(Date.now() + request.expiresIn * 1000)
});
// Generate QR code
const qrCode = await generateQRCode({
type: 'PresentationRequest',
requestId: response.id,
url: `${window.location.origin}/submit/${response.id}`
});
setQRCode(qrCode);
toast.success('Verification request created!');
} catch (error) {
toast.error('Failed to create request');
}
};
return (
<div className="create-request">
<Input
label="Verification Purpose"
value={request.purpose}
onChange={(e) => setRequest({ ...request, purpose: e.target.value })}
placeholder="e.g., Age verification for entry"
/>
<div className="credentials-section">
<h3>Required Credentials</h3>
{request.requiredCredentials.map((req, index) => (
<CredentialRequirementEditor
key={index}
requirement={req}
onChange={(updated) => updateRequirement(index, updated)}
useZKP={useZKP}
/>
))}
<Button onClick={addCredentialRequirement}>
Add Requirement
</Button>
</div>
<Toggle
label="Use Zero-Knowledge Proofs"
checked={useZKP}
onChange={setUseZKP}
/>
<Select
label="Request Expires In"
value={request.expiresIn}
onChange={(val) => setRequest({ ...request, expiresIn: val })}
options={[
{ value: 300, label: '5 minutes' },
{ value: 3600, label: '1 hour' },
{ value: 86400, label: '24 hours' }
]}
/>
<Button onClick={createRequest} variant="primary">
Create Request & Generate QR
</Button>
</div>
);
};
2. QR Code Display
Show QR code for users to scan with their wallet.Copy
import QRCode from 'qrcode.react';
const QRDisplay = ({ requestId, requestUrl }) => {
const [copied, setCopied] = useState(false);
const qrData = JSON.stringify({
type: 'PresentationRequest',
requestId,
url: requestUrl
});
const copyLink = () => {
navigator.clipboard.writeText(requestUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="qr-display">
<div className="qr-code-container">
<QRCode
value={qrData}
size={300}
level="H"
includeMargin={true}
/>
</div>
<div className="instructions">
<p>Scan this QR code with Sphyre ALV wallet</p>
<p className="text-sm text-gray-600">
Or share the link below
</p>
</div>
<div className="link-section">
<Input
value={requestUrl}
readOnly
onClick={(e) => e.target.select()}
/>
<Button onClick={copyLink}>
{copied ? 'Copied!' : 'Copy Link'}
</Button>
</div>
<div className="waiting-state">
<Spinner />
<p>Waiting for user response...</p>
</div>
</div>
);
};
3. Receive and Verify Presentation
Process submitted presentations from users.Copy
const PresentationPage = () => {
const [presentations, setPresentations] = useState([]);
const [selectedPresentation, setSelectedPresentation] = useState(null);
useEffect(() => {
fetchPresentations();
// Set up polling for new presentations
const interval = setInterval(fetchPresentations, 5000);
return () => clearInterval(interval);
}, []);
const fetchPresentations = async () => {
const data = await apiService.getPresentations(getVerifierDid());
setPresentations(data);
};
const verifyPresentation = async (presentation) => {
setVerifying(true);
try {
const result = await apiService.verifyPresentation(presentation.id);
setVerificationResult(result);
if (result.valid) {
toast.success('Verification successful!');
} else {
toast.error('Verification failed');
}
} catch (error) {
toast.error('Verification error');
} finally {
setVerifying(false);
}
};
return (
<div className="presentation-page">
<div className="presentations-list">
<h2>Received Presentations</h2>
{presentations.map(pres => (
<PresentationCard
key={pres.id}
presentation={pres}
onClick={() => setSelectedPresentation(pres)}
/>
))}
</div>
{selectedPresentation && (
<PresentationModal
presentation={selectedPresentation}
onVerify={verifyPresentation}
onClose={() => setSelectedPresentation(null)}
/>
)}
</div>
);
};
4. Verification Logic
Cryptographically verify credentials.Copy
class VerificationService {
async verifyPresentation(presentation: VerifiablePresentation): Promise<VerificationResult> {
const results = [];
// 1. Verify holder's signature on presentation
const holderSignatureValid = await this.verifyHolderSignature(presentation);
if (!holderSignatureValid) {
return {
valid: false,
error: 'Invalid holder signature'
};
}
// 2. Verify each credential
for (const credential of presentation.verifiableCredential) {
const credentialResult = await this.verifyCredential(credential);
results.push(credentialResult);
}
// 3. Check if all credentials are valid
const allValid = results.every(r => r.valid);
return {
valid: allValid,
credentials: results,
verifiedClaims: this.extractVerifiedClaims(results),
timestamp: new Date()
};
}
async verifyCredential(credential: VerifiableCredential): Promise<CredentialVerificationResult> {
// 1. Resolve issuer DID to get public key
const issuerDID = credential.issuer;
const didDocument = await this.resolveDID(issuerDID);
const publicKey = didDocument.verificationMethod[0].publicKeyBase64;
// 2. Verify issuer's signature
const signatureValid = await this.verifyIssuerSignature(
credential,
publicKey
);
// 3. Check blockchain anchor
const anchorValid = await this.verifyBlockchainAnchor(
credential.ipfsHash
);
// 4. Check revocation status
const isRevoked = await this.checkRevocationStatus(credential.id);
// 5. Check expiration
const isExpired = credential.expirationDate &&
new Date(credential.expirationDate) < new Date();
// 6. Verify issuer is trusted
const issuerTrusted = await this.isIssuerTrusted(issuerDID);
return {
valid: signatureValid && anchorValid && !isRevoked && !isExpired && issuerTrusted,
checks: {
signature: signatureValid,
anchor: anchorValid,
revoked: !isRevoked,
expired: !isExpired,
trustedIssuer: issuerTrusted
},
credential
};
}
async verifyIssuerSignature(
credential: VerifiableCredential,
publicKey: string
): Promise<boolean> {
// Extract credential without proof
const { proof, ...credentialData } = credential;
// Verify signature using public key
return verifyDilithiumSignature(
JSON.stringify(credentialData),
proof.signature,
publicKey
);
}
async verifyBlockchainAnchor(ipfsHash: string): Promise<boolean> {
const contract = getBlockchainContract();
return await contract.verifyAnchor(ipfsHash);
}
async checkRevocationStatus(credentialId: string): Promise<boolean> {
const contract = getBlockchainContract();
return await contract.isRevoked(credentialId);
}
}
5. Verification Result Display
Show verification results to the verifier.Copy
const VerificationResult = ({ result }: { result: VerificationResult }) => {
return (
<div className="verification-result">
<div className={`result-header ${result.valid ? 'success' : 'error'}`}>
{result.valid ? (
<>
<CheckCircle size={48} />
<h2>Verification Successful</h2>
</>
) : (
<>
<XCircle size={48} />
<h2>Verification Failed</h2>
</>
)}
</div>
{result.valid && (
<div className="verified-claims">
<h3>Verified Information</h3>
{Object.entries(result.verifiedClaims).map(([key, value]) => (
<div key={key} className="claim-row">
<span className="claim-label">{formatLabel(key)}:</span>
<span className="claim-value">{value}</span>
<CheckCircle size={16} className="text-green-500" />
</div>
))}
</div>
)}
<div className="verification-details">
<h3>Verification Details</h3>
{result.credentials?.map((cred, index) => (
<CredentialCheckResult key={index} credential={cred} />
))}
</div>
<div className="actions">
<Button onClick={() => downloadReport(result)}>
Download Report
</Button>
<Button variant="outline" onClick={() => saveVerification(result)}>
Save Verification
</Button>
</div>
</div>
);
};
const CredentialCheckResult = ({ credential }) => {
const checks = credential.checks;
return (
<div className="credential-check">
<h4>{credential.credential.type[1]}</h4>
<div className="checks-list">
<CheckItem
label="Signature Valid"
status={checks.signature}
/>
<CheckItem
label="Blockchain Anchor"
status={checks.anchor}
/>
<CheckItem
label="Not Revoked"
status={checks.revoked}
/>
<CheckItem
label="Not Expired"
status={checks.expired}
/>
<CheckItem
label="Trusted Issuer"
status={checks.trustedIssuer}
/>
</div>
</div>
);
};
6. Zero-Knowledge Proof Verification
Verify ZKP presentations without seeing raw data.Copy
const ZKPVerification = ({ presentation }: { presentation: ZKProof }) => {
const [verifying, setVerifying] = useState(false);
const [result, setResult] = useState(null);
const verifyZKP = async () => {
setVerifying(true);
try {
// Verify the zero-knowledge proof
const isValid = await zkpService.verify({
proof: presentation.proof,
statement: presentation.statement,
publicInput: presentation.publicInput,
commitment: presentation.commitment
});
// Verify credential is valid (not revoked)
const credentialValid = await verifyCredentialValidity(
presentation.credentialId,
presentation.issuerDid
);
setResult({
valid: isValid && credentialValid,
statement: presentation.statement,
proofType: presentation.proofType
});
} catch (error) {
setResult({ valid: false, error: error.message });
} finally {
setVerifying(false);
}
};
return (
<div className="zkp-verification">
<div className="statement-display">
<h3>Statement to Verify:</h3>
<p className="statement">{presentation.statement}</p>
</div>
<div className="proof-info">
<InfoRow label="Proof Type" value={presentation.proofType} />
<InfoRow label="Issuer" value={truncateDid(presentation.issuerDid)} />
</div>
<Button onClick={verifyZKP} disabled={verifying}>
{verifying ? 'Verifying...' : 'Verify Proof'}
</Button>
{result && (
<div className={`result ${result.valid ? 'valid' : 'invalid'}`}>
{result.valid ? (
<>
<CheckCircle />
<p>Statement proven: <strong>{result.statement}</strong></p>
<p className="note">No personal data was revealed</p>
</>
) : (
<>
<XCircle />
<p>Proof verification failed</p>
</>
)}
</div>
)}
</div>
);
};
Verification History
Track all verifications for audit and compliance.Copy
const VerificationHistory = () => {
const [verifications, setVerifications] = useState([]);
const [filter, setFilter] = useState({
dateFrom: null,
dateTo: null,
status: 'all'
});
const exportVerifications = () => {
const csv = convertToCSV(verifications);
downloadCSV(csv, 'verifications.csv');
};
return (
<div className="verification-history">
<div className="header">
<h1>Verification History</h1>
<Button onClick={exportVerifications}>
Export Report
</Button>
</div>
<FilterPanel filter={filter} onChange={setFilter} />
<Table>
<thead>
<tr>
<th>Date</th>
<th>Holder DID</th>
<th>Credential Type</th>
<th>Result</th>
<th>Purpose</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{verifications.map(ver => (
<tr key={ver.id}>
<td>{formatDateTime(ver.timestamp)}</td>
<td>{truncateDid(ver.holderDid)}</td>
<td>{ver.credentialType}</td>
<td>
<Badge status={ver.valid ? 'success' : 'error'}>
{ver.valid ? 'Valid' : 'Invalid'}
</Badge>
</td>
<td>{ver.purpose}</td>
<td>
<Button size="sm" onClick={() => viewDetails(ver)}>
View
</Button>
</td>
</tr>
))}
</tbody>
</Table>
</div>
);
};
Trust Registry
Manage trusted credential issuers.Copy
const TrustRegistry = () => {
const [trustedIssuers, setTrustedIssuers] = useState([]);
const [newIssuerDid, setNewIssuerDid] = useState('');
const addTrustedIssuer = async () => {
await apiService.addTrustedIssuer({
verifierId: getVerifierDid(),
issuerDid: newIssuerDid
});
toast.success('Issuer added to trust registry');
fetchTrustedIssuers();
setNewIssuerDid('');
};
const removeTrustedIssuer = async (issuerDid) => {
await apiService.removeTrustedIssuer(issuerDid);
toast.info('Issuer removed from trust registry');
fetchTrustedIssuers();
};
return (
<div className="trust-registry">
<h1>Trusted Issuers</h1>
<div className="add-issuer">
<Input
placeholder="Enter issuer DID"
value={newIssuerDid}
onChange={(e) => setNewIssuerDid(e.target.value)}
/>
<Button onClick={addTrustedIssuer}>Add Issuer</Button>
</div>
<div className="issuers-list">
{trustedIssuers.map(issuer => (
<IssuerCard
key={issuer.did}
issuer={issuer}
onRemove={() => removeTrustedIssuer(issuer.did)}
/>
))}
</div>
</div>
);
};
API Integration
Copy
class VerifierApiService {
baseURL = 'https://api.sphyre.tech';
async createPresentationRequest(request: PresentationRequest) {
return await this.post('/api/verifier/request', request);
}
async getPresentations(verifierId: string) {
return await this.get(`/api/verifier/presentations?verifier=${verifierId}`);
}
async verifyPresentation(presentationId: string) {
return await this.post(`/api/verifier/verify/${presentationId}`);
}
async getVerificationHistory(verifierId: string, filters?: any) {
const params = new URLSearchParams(filters);
return await this.get(`/api/verifier/history?${params}`);
}
async addTrustedIssuer(data: { verifierId: string, issuerDid: string }) {
return await this.post('/api/verifier/trust', data);
}
}
Best Practices
Security
Security
- Always verify blockchain anchors
- Check revocation status
- Validate issuer trust
- Use HTTPS only
- Implement rate limiting
Privacy
Privacy
- Request only necessary data
- Use ZKP when possible
- Don’t store personal data
- Clear purpose statements
- Respect user consent
Compliance
Compliance
- GDPR compliance
- Audit trail maintenance
- Data retention policies
- User rights support
- Regular compliance reviews
User Experience
User Experience
- Clear instructions
- Fast verification
- Mobile-friendly
- Error handling
- Accessibility