This document outlines the security properties, threat model, and mitigations implemented in Uniquity.
- Security Properties
- Threat Model
- Attack Vectors & Mitigations
- Trust Assumptions
- FHE Security
- Cryptographic Choices
- Known Limitations
Uniquity provides the following security guarantees:
| Property | Guarantee |
|---|---|
| Confidentiality | Biometric embeddings are never revealed to any party |
| No Linkability | Cannot link verifications across platforms |
| No Reversibility | Cannot reconstruct face from stored data |
| Property | Guarantee |
|---|---|
| End-to-End Encryption | Only admin can read submission content |
| Key Confidentiality | AES keys protected by FHE |
| Unlinkability | Cannot link submissions without decryption |
| Property | Guarantee | Status |
|---|---|---|
| Uniqueness | One verification per biometric identity | 🔜 v2 |
| Non-Transferability | Credentials bound to wallet address | ✅ v1 |
| Temporal Validity | Verification persists until revoked | ✅ v1 |
| Encrypted Storage | Biometrics stored encrypted for future comparison | ✅ v1 |
Note: In v1 (current), users are verified instantly. Uniqueness comparison will be enforced in v2 using off-chain FHE computation. Encrypted embeddings are stored now to enable this future comparison.
┌─────────────────────────────────────────────────────────────────┐
│ THREAT ACTORS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Malicious User │ │ Network Observer│ │ Malicious Admin │ │
│ │ ───────────── │ │ ───────────── │ │ ───────────── │ │
│ │ • Sybil attack │ │ • Traffic │ │ • Data leak │ │
│ │ (v2 mitigated)│ │ analysis │ │ • False reject │ │
│ │ • Replay attack│ │ • Metadata │ │ • Collusion │ │
│ │ • Impersonation│ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Comparison Svc │ │ Compromised Node│ │ State Adversary │ │
│ │ (v2) │ │ ───────────── │ │ ───────────── │ │
│ │ ───────────── │ │ • Data extract │ │ • Coercion │ │
│ │ • Wrong result │ │ • Manipulation │ │ • Backdoor │ │
│ │ • Denial │ │ • Censorship │ │ • Surveillance │ │
│ │ • Collusion │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
- Biometric Embeddings - Most sensitive; never to be revealed
- Submission Content - Business sensitive; admin-only access
- AES Keys - Enables decryption; must be FHE-protected
- Verification Status - Public but manipulation-resistant
- User Privacy - Pseudonymity and unlinkability
Vector: Creating multiple accounts with same biometric
Attacker ──▶ Extract face embedding
──▶ Modify slightly
──▶ Attempt multiple verifications
v1 Status: Not mitigated (instant verification without comparison)
v2 Mitigation:
- Manhattan distance computed on encrypted values (off-chain)
- Threshold (400,000) calibrated for reasonable variation
- Cannot test distance before submission (encrypted)
- Comparison service computes FHE distance without seeing plaintext
// v2: Distance comparison happens on encrypted values (off-chain)
// Service submits encrypted minDistance result
// User cannot manipulate the comparisonVector: Reusing captured biometric data
Attacker ──▶ Capture victim's face image
──▶ Submit as own verification
Mitigation:
- Liveness detection in face-api.js (blink, movement)
- Input proof tied to specific user address
- Timestamp validation on-chain
// Input is bound to user address
const input = fhevmInstance.createEncryptedInput(
contractAddress,
userAddress // Proof is specific to this address
);Vector: Submitting fake cleartext values with forged proof
Attacker ──▶ Observe publicDecrypt result
──▶ Modify cleartext (e.g., set distance = MAX_INT)
──▶ Submit fake value with original proof
Mitigation:
FHE.checkSignatures()cryptographically binds cleartext to ciphertext- KMS signatures cannot be forged without Zama's private keys
- Proof is only valid for exact (handle, cleartext) pair
- Order of handles matters - proof is order-dependent
function finalizeVerification(
uint256 requestId,
uint64 clearDistance,
bytes calldata decryptionProof
) external {
bytes32[] memory handles = new bytes32[](1);
handles[0] = req.minDistanceHandle;
bytes memory abiEncodedClear = abi.encode(clearDistance);
// Reverts if proof doesn't match (clearDistance, handle) pair
FHE.checkSignatures(handles, abiEncodedClear, decryptionProof);
// Only reaches here if proof is valid
// ...
}Vector: Observing pending verification and racing to register
Attacker ──▶ Monitor mempool for proveHumanity()
──▶ Extract encrypted values (cannot decrypt)
──▶ ??? (no viable attack)
Mitigation:
- Encrypted values cannot be reused (bound to address)
- Even with same face, attacker's proof would be different
- v2: Profile comparison happens against all existing profiles
Vector: Admin private key stolen, all submissions decrypted
Attacker ──▶ Steal admin key
──▶ Call getSubmissionKeyHandle()
──▶ userDecrypt all keys
──▶ Decrypt all submissions
Mitigation:
- Per-campaign admin isolation
- Compromise affects only that campaign's submissions
- Cannot retroactively access other campaigns
- Recommended: Use multisig for admin
Vector: Extracting biometric data from on-chain storage
Attacker ──▶ Read contract storage
──▶ Extract euint64 chunks
──▶ ??? Decrypt ???
Mitigation:
- Values are FHE-encrypted with network key
- Only Zama KMS can decrypt
- No party (including contract) can read plaintext
- Chunks are meaningless without decryption
Vector: Correlating on-chain activity with identity
Observer ──▶ Track wallet addresses
──▶ Correlate verification timing
──▶ Link to real identity
Mitigation:
- Use fresh wallet for verification
- Verification doesn't reveal which campaign
- Submission content is encrypted
Vector: Malicious comparison service returns wrong distance
Attacker ──▶ Run compromised comparison service
──▶ Always return MAX_INT (everyone unique)
──▶ Or return 0 (everyone duplicate)
Mitigation:
- Service cannot see decrypted biometrics (FHE)
- All operations emit events for audit trail
- Multiple services can verify each other
- Future: ZK proofs of correct computation
- Future: Zama coprocessor with cryptographic guarantees
| Component | Trust Assumption | Version |
|---|---|---|
| Zama KMS | Correctly signs decryption proofs; won't forge signatures | v1, v2 |
| Zama Relayer | Faithfully relays decryption requests to KMS | v1, v2 |
| FHE Security | TFHE encryption is computationally secure | v1, v2 |
| checkSignatures | Correctly verifies KMS signatures on-chain | v2 |
| face-api.js | Provides accurate, consistent embeddings | v1, v2 |
| Browser | Client-side code executes correctly | v1, v2 |
| IPFS | Data availability (not confidentiality) | v1, v2 |
| Comparison Service | Correctly computes FHE distance (auditable) | v2 |
| Component | Why |
|---|---|
| Blockchain Nodes | Can't see encrypted data |
| Contract Owner | No admin keys for Core contract |
| Campaign Admins | Can only decrypt their campaign's submissions |
| Other Users | Isolated by FHE permissions |
| Network | All sensitive data encrypted in transit |
┌─────────────────────────────────────────────────────────────────┐
│ TRUST HIERARCHY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ MAXIMUM TRUST │
│ ───────────── │
│ Zama FHE Network (KMS, Relayer, Coprocessor) │
│ • KMS signs decryption proofs │
│ • checkSignatures verifies on-chain │
│ │ │
│ ▼ │
│ HIGH TRUST (v2 only) │
│ ───────────── │
│ Comparison Service (off-chain FHE computation) │
│ • Computes encrypted distance │
│ • Cannot see plaintext (FHE) │
│ • Auditable via event logs │
│ │ │
│ ▼ │
│ MODERATE TRUST │
│ ────────────── │
│ User's Browser (executes client code) │
│ • Calls publicDecrypt off-chain (v2) │
│ • Submits proof on-chain (v2) │
│ │ │
│ ▼ │
│ MINIMAL TRUST │
│ ───────────── │
│ Blockchain (can't see encrypted data) │
│ IPFS (encrypted storage) │
│ Campaign Admins (isolated access) │
│ │ │
│ ▼ │
│ ZERO TRUST │
│ ────────── │
│ Other Users │
│ Network Observers │
│ Contract Storage Readers │
│ │
└─────────────────────────────────────────────────────────────────┘
Uniquity uses Zama's fhEVM which implements FHE (Fully Homomorphic Encryption):
| Parameter | Value |
|---|---|
| Security Level | 128-bit |
| Key Size | Network-managed |
| Ciphertext Expansion | ~8x |
| Supported Types | euint8, euint16, euint32, euint64, euint256 |
All FHE operations maintain semantic security:
// These operations never reveal plaintext
euint64 sum = FHE.add(a, b); // a, b, sum all encrypted
ebool lt = FHE.lt(a, b); // Result is encrypted boolean
euint64 result = FHE.select(lt, a, b); // Encrypted selectionFHE permissions control who can decrypt:
// Grant permissions (critical for security)
FHE.allow(encryptedKey, campaign.admin); // Admin can decrypt
FHE.allowThis(encryptedKey); // Contract can operate
FHE.allow(encryptedKey, msg.sender); // User can edit
// Without permission, decryption fails
// Even contract owner cannot bypass permissions| Algorithm | Use Case | Rationale |
|---|---|---|
| FHE | Biometric storage & comparison (v2) | Enables encrypted computation |
| AES-256-GCM | Submission encryption | Fast, authenticated encryption |
| SHA3-256 | CID hashing | Collision resistance for validation |
Converting float32 embeddings to FHE-compatible format:
// Original: 128 × float32 = 4096 bits
// Quantized: 4 × uint64 = 256 bits
// Compression: 16x (with acceptable precision loss)
// Quantization preserves relative distances
// Tests show >95% correlation with original distancesSIMILARITY_THRESHOLD = 400,000
Calibrated to balance:
├── False Positive Rate (same person rejected): < 1%
├── False Negative Rate (different person accepted): < 0.1%
└── Lighting/angle tolerance: ±15 degrees, ±20% brightness
Bucket-based comparison reduces:
├── Comparison scope: O(n/8) vs O(n)
└── Still maintains security (compare within bucket)
-
No Uniqueness Enforcement
- Users verified instantly without comparison
- Same person can verify with multiple wallets
- Mitigation: Encrypted embeddings stored for v2 comparison
-
Liveness Detection
- Browser-based, could be spoofed with effort
- Mitigation: Threshold distance catches most spoofs (v2)
-
KMS Trust
- Decryption proofs rely on Zama KMS signatures
- Mitigation:
FHE.checkSignatures()cryptographically verifies
-
Comparison Service Trust
- Service must be trusted to compute correctly
- Cannot see encrypted data (FHE protected)
- Mitigation: Audit logs, multiple services, future ZK proofs
-
Replay Protection
- Same proof could theoretically be resubmitted
- Mitigation: Track finalized requests; reject duplicates
-
Bucket Collision
- Users in same bucket compared together
- Mitigation: 8 buckets distribute load evenly
-
Comparison Latency
- Off-chain FHE computation takes time (~10-30 seconds)
- Mitigation: Async flow with status updates
-
Multi-Factor Verification
- Add voice embedding
- Combine multiple biometrics
-
Decentralized Comparison
- Replace single service with MPC network
- Or use Zama coprocessor when available
- No single point of trust
-
ZK Proofs of Computation
- Service generates ZK proof of correct FHE execution
- Fully trustless comparison
-
Revocation Mechanism
- Allow users to revoke verification
- Re-verify with updated biometrics
-
Cross-Chain Verification
- Portable credentials across chains
- Bridge verified status
| Component | Status | Notes |
|---|---|---|
| UniquityCore.sol | 🔶 Internal Review | Pending external audit |
| UniquitySubmit.sol | 🔶 Internal Review | Pending external audit |
| Client-side crypto | 🔶 Internal Review | Uses standard libraries |
| FHE operations | ✅ Zama Audited | Part of fhEVM |
| Comparison Service | 🔜 v2 | Not yet implemented |
If you discover a security vulnerability:
- Do NOT disclose publicly
- Email: bossfemzy10@gmail.com
- Include: Description, steps to reproduce, impact
- We will respond within 48 hours