?

Side quest 03Digital identity

Aadhaar (UIDAI).

The World's Largest Biometric Identity System

INTERMEDIATE42 MIN READ6 PHASES+120 XP

Mission brief

Design a national biometric identity system

Quest log0/7 stages · 0/5 checkpoints · 0/7 quick checks
Aadhaar numbers issued (144.8 crore, June 2026)
1.45B
authentications a day (nearly 100M by September 2026)
90M+
for a 1:1 authentication
< 200 ms
peak authentications per second (our estimate)
~3,000

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 15-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases

Want it harder? Hide the model answer and work each phase in a scratchpad, then reveal and score yourself. Or simply read on: the checkpoints below work either way.

Intro · 2 min

Identity at Billion Scale

Imagine this challenge: You need to uniquely identify 1.4 billion people.

Not just assign them a number — but guarantee that each person appears exactly once in your system. No duplicates. No fakes. Every identity verifiable in under 200 milliseconds.

You'll need to match billions of fingerprints against billions of other fingerprints. Trillions of biometric comparisons. Every single day.

This is Aadhaar — and it's the largest biometric identity system ever built.

Aadhaar · 1:1 authentication checks one stored template, 1:N dedup searches every template across three ABIS vendors
LIVEAadhaar · 1:1 authentication checks one stored template, 1:N dedup searches every template across three ABIS vendors
The Aadhaar scale (as of 2026)
Enrollment
  • Aadhaar numbers issued: ~1.45 Billion (144.8 crore, UIDAI saturation report, 30 June 2026), about 1.35 billion live after estimated deaths
  • Coverage: ~95% of the population (live Aadhaar, June 2026)
  • Biometric data: ~10-15 Petabytes (10 fingerprints + 2 iris scans + photo per person; UIDAI's 2014 estimate)
Authentication
  • Daily authentications: 90+ Million ("nearly 10 crore" a day per UIDAI, September 2026)
  • Monthly authentications: 2.1-2.3 Billion (e.g. 211 crore in May, 231 crore in November 2025)
  • Cumulative (to date): 170+ Billion authentications (March 2026)
  • e-KYC transactions: ~47 Crore (470 Million)/month (November 2025)
  • Face authentication: ~28 Crore (280 Million)/month (November 2025, AI-powered)
Performance
  • Authentication latency: < 200ms
  • Availability: 99.9%+ (target)
  • Entities using the Aadhaar platform: 600+ (UIDAI chairman, September 2026)
Deduplication
  • Biometric matches/day: 600+ Trillion (at the ~1M/day enrolment peak, 2014)
  • ABIS vendors: 3 (for accuracy and throughput)
  • Duplicates caught: 99.965% (FNIR 0.035%, UIDAI's 2012 measurement)
Impact
  • DBT savings: ₹3.48 Lakh Crore ($40B) saved by Direct Benefit Transfer, 2009-2024 (an analysis reported in April 2025; it credits Aadhaar authentication with a critical role)
  • Ghost beneficiaries: Millions eliminated (government estimates)
  • Bank accounts linked: Hundreds of millions

This is the system we'll design today — and understand the engineering marvel behind proving "you are you" at planetary scale.

ExplainerThe whole quest: Aadhaar

Aadhaar has issued 1.45 billion numbers and answers 90 million+ authentications a day in under 200 ms, while brute-force deduplication would take 3,170 years per day of enrolment. Offline kits encrypt enrolment packets for CIDR, where three ABIS vendors must agree 2 of 3; banks (AUAs) reach CIDR through ASAs and get back only a signed Yes or No. The deep dives cover full-gallery dedup, the 200 ms budget (80 ms for the match), PKI, HSMs, Virtual IDs and face authentication, then village-scale enrolment, fallbacks and defences against replayed or cloned fingerprints.

The Interview Begins#

You're interviewing at a government technology agency. The Chief Architect draws on the whiteboard:

Interviewer

"India's Aadhaar is often cited as the world's most ambitious digital identity project. I want you to design a biometric identity system that can scale to a billion people. Walk me through how you'd approach it."

Design a National Biometric Identity System

Build an identity system that can:

Requirements
  • Enroll 1+ billion residents with biometrics
  • Guarantee uniqueness (no duplicates in the system)
  • Authenticate identity in real-time (< 500ms)
  • Handle 100+ million authentications per day
  • Work across 640,000 villages with unreliable connectivity
  • Protect biometric data with highest security
  • Provide e-KYC (Know Your Customer) service
  • Support multiple authentication modes (fingerprint, iris, OTP)
  • 99.9%+ availability

Constraint: This is a government project with vendor neutrality

Interviewer

"The uniqueness guarantee is the hardest part. You need to prove that each of 1.4 billion people appears exactly once. That's never been done before at this scale."

Phase 1 · Stage 1 of 7 · 2 min

Phase 1: Requirements Clarification#

You

"Let me understand the specific challenges before designing."

Your Questions#

Transcript
You

"First, what biometric modalities are we capturing? And what's the expected quality given enrollment happens in remote villages?"

Interviewer

"10 fingerprints, 2 iris scans, and a photograph. Quality will vary — many manual laborers have worn fingerprints, elderly may have faded prints, some people lack fingers. The system must handle all cases."

You

"For uniqueness, what's the acceptable error rate? False accepts (enrolling duplicates) vs false rejects (wrongly rejecting unique people)?"

Interviewer

"This is critical. A false accept means someone gets two identities — they can claim benefits twice. A false reject means a legitimate person can't get enrolled. Both are bad, but false accepts are worse for a welfare system."

You

"What about authentication? Is it 1:1 matching (verify this person is who they claim) or 1:N (find this person in the database)?"

Interviewer

"Authentication is always 1:1 — they provide their Aadhaar number plus biometric, we verify it matches the stored data. Deduplication during enrollment is 1:N — we search the entire database to ensure they're not already enrolled."

You

"What infrastructure can we assume in remote areas?"

Interviewer

"Minimal. Many areas have no internet, unreliable power, extreme temperatures. Enrollment must work offline. Authentication needs connectivity but should degrade gracefully."

Requirements Summary#

Functional Requirements:

Functional requirements
1. Enrollment
  • Capture demographics (name, address, DOB, gender)
  • Capture biometrics (10 fingerprints, 2 iris, 1 photo)
  • Verify supporting documents (proof of identity/address)
  • Perform deduplication (1:N match against entire database)
  • Generate unique 12-digit Aadhaar number
  • Print and mail physical Aadhaar letter
2. Authentication
  • Demographic authentication (name/address matching)
  • Biometric authentication (fingerprint, iris, face)
  • OTP authentication (via registered mobile)
  • Multi-factor authentication (combinations)
  • Return only Yes/No (no PII in response)
3. e-KYC (Know Your Customer)
  • Return verified identity data after authentication
  • Digitally signed response
  • Replace paper-based KYC for banks, telecom, etc.
4. Update
  • Demographics update (address, phone, etc.)
  • Biometrics update (for degraded prints)
  • Document-based or operator-assisted updates
5. Privacy features
  • Virtual ID (16-digit temporary alias for Aadhaar)
  • Masked Aadhaar (partially hidden number)
  • Authentication history (user can see who queried)

Non-Functional Requirements:

Non-functional requirements
Scale
  • 1.4 billion enrolled residents
  • 90+ million authentications/day
  • Peak: ~3,000 authentications/second
  • 15+ petabytes of biometric data
Latency
  • Authentication: < 200ms (1:1 match)
  • Deduplication: minutes (1:N against billions)
Accuracy
  • False Positive Identification Rate (FPIR): < 0.06% (UIDAI measured 0.057% in 2012)
  • False Negative Identification Rate (FNIR): < 0.035% (UIDAI measured 0.035%)
Availability
  • 99.9%+ uptime
  • Geo-distributed for disaster recovery
Security
  • 2048-bit PKI encryption
  • Data encrypted at rest and in transit
  • HSM for key management
  • No biometric data leaves CIDR

Phase 2 · Stage 2 of 7 · 3 min

Phase 2: Back of the Envelope Estimation#

You

"Let me work through the numbers to understand the computational challenge."

Your turn · checkpoint 1not answered

Before the numbers: 1:1 versus 1:N

Authenticating someone compares their fingerprint with the one record their number points to. Enrolling someone must prove they are not already among a billion people with ten fingerprints each. At a million enrolments a day and 100,000 matches a second, how long would brute-force deduplication take? What does that tell you about the design?

Think it through, or jot it down. Then compare.

One strong answer

  • Templates on file: 1B people × 10 fingers = 10 billion.
  • Comparisons: 1M new people × 10B templates = 10^16 a day.
  • Time: 10^16 ÷ 10^5 per second = 10^11 seconds, about 3,170 years for each day of enrolment.

So two very different paths: authentication is 1:1, synchronous and has to finish in milliseconds, while deduplication is 1:N, runs asynchronously (minutes to days are fine) and needs biometric indexing, multi-modal fusion and massively parallel matching hardware to be possible at all.

The Deduplication Challenge#

WorksheetThe impossible math

To guarantee uniqueness, every new enrollment must be compared against EVERY existing record — for 1 billion people with 10 fingerprints each:

  • Fingerprint comparisons for new enrollment: 1,000,000,000 people × 10 fingers = 10 billion templates
  • If we enroll 1 million new people per day: 1,000,000 × 10 billion = 10,000,000,000,000,000 comparisons/day = 10 quadrillion matches/day!
  • At traditional matching speed (100,000 matches/sec): 10^16 / 10^5 = 10^11 seconds = 3,170 years per day of enrollment!

This is mathematically impossible with brute force.

The Solution: Multi-Modal + Multi-ABIS#

Making deduplication tractable
1. Demographic pre-filter

Before biometric matching, filter by:

  • Name phonetics
  • Date of birth
  • Gender
  • Geographic region

This can cut the search space by 99%+, but only as a cheap first check: a fraudster can change name, DOB or region, so Aadhaar's ABIS still searches biometrics against the full gallery (see Deep Dive 1)

2. Multi-modal biometrics

Using fingerprint + iris together:

  • Fingerprint alone: highly distinctive, but worn or faded prints raise error rates
  • Iris alone: very low false-match rates, unaffected by manual labour
  • Combined: independent errors multiply, so the fused error rate is far lower than either alone

The combination allows lower thresholds per modality

3. Hierarchical matching
  • First: Fast, approximate match (GPU-accelerated)
  • If potential match: Detailed matching
  • If still ambiguous: Human adjudication
4. Three ABIS vendors
  • Aadhaar shares the deduplication load across three vendors, and a suspected duplicate from one is sent to another ABIS to verify
  • This design goes further: every enrollment goes to all three, and 2 of 3 must agree
  • Different algorithms catch different edge cases

Storage Estimation#

WorksheetBiometric data storage
  • Per person · 10 fingerprints: 10 × 10KB template = ~100KB
  • Per person · 2 iris scans: 2 × 50KB template = ~100KB
  • Per person · 1 photograph: ~50KB
  • Per person · Demographics: ~2KB
  • Per person · Metadata: ~5KB
  • Total per person: ~257KB
  • For 1.4 billion people: 1.4B × 257KB = 360 TB (templates only)
  • Raw biometric images (archived): per person ~5MB (high-res captures); total 1.4B × 5MB = 7 PB
  • With replication (3x): ~20 PB total storage

Authentication Traffic#

Authentication load
Traffic
  • Daily authentications: 90,000,000
  • Seconds per day: 86,400
  • Average TPS: ~1,040 auth/second
  • Peak multiplier: 3x
  • Peak TPS: ~3,000 auth/second
Each authentication requires
  1. Decrypt request (PKI)
  2. Lookup Aadhaar record
  3. Biometric 1:1 match
  4. Sign response
Time budget: 200ms total
  • Network: 50ms
  • Crypto: 30ms
  • Lookup: 20ms
  • Match: 80ms
  • Overhead: 20ms
Quick check3 questions · +5 XP each

Requirements and estimation

  1. Q1/3 Why can authentication finish in under 200ms while deduplication takes minutes or longer?

    Answer & explanation

    Answer: C

    The resident supplies their Aadhaar number, so authentication compares against one stored template. Enrollment has no number yet and must search the whole database. Biometric authentication is still a real match; what makes it fast is having only one candidate.

  2. Q2/3 Using the estimate's 90 million authentications a day and a 3x peak multiplier, what peak rate should CIDR handle?

    Answer & explanation

    Answer: D

    90M / 86,400 s is about 1,040 per second on average; 3x that is about 3,000 per second at peak. 1,000 per second is the average, so sizing to it would leave no room for the daily peak.

  3. Q3/3 For a welfare system, the interviewer says false accepts are worse than false rejects. Why?

    Answer & explanation

    Answer: A

    A false accept enrolls a duplicate, which is exactly the fraud the uniqueness guarantee exists to stop. Blocking a genuine resident is the false reject, which is bad but can be handled with review and re-enrollment.

Phase 3 · Stage 3 of 7 · 4 min

Phase 3: High-Level Architecture#

You

"Aadhaar's architecture follows four key principles: openness, linear scalability, strong security, and vendor neutrality."

Your turn · checkpoint 2not answered

Sketch it first: who may touch a fingerprint?

Banks, telecoms and government schemes all need to check identities. Sketch who talks to whom for enrolment and for authentication. Should a bank ever receive a stored fingerprint?

Think it through, or jot it down. Then compare.

One strong answer

  • Enrolment: an offline-capable client captures and encrypts everything at capture time, a registrar uploads the packets, and the central repository (CIDR, two active-active data centres never exposed to the internet) runs deduplication through independent ABIS vendors.
  • Authentication: the requesting agency (AUA, say a bank) sends the request through a licensed service agency (ASA) over dedicated lines to CIDR.
  • No, never. The bank forwards a biometric captured and encrypted on the device with CIDR's public key. Only CIDR can decrypt it, CIDR does the 1:1 match, and it answers with a signed yes/no and a transaction ID. Identity details come back only for e-KYC, with the resident's consent.

The Aadhaar Ecosystem#

DiagramAadhaar architecture — enrollment ecosystem
%% title: Aadhaar architecture — enrollment ecosystem
flowchart TD
  RES@{ icon: "sd:user", label: "Resident (Village)" } --> EA@{ icon: "sd:building", label: "Enrollment Agency" }
  EA --> RG@{ icon: "sd:building", label: "Registrar (State Govt)" }
  EA ==>|"Encrypted packet"| CIDR
  RG -->|"Verification"| CIDR
  subgraph CIDR["CIDR (Central DB) · deduplication"]
    direction TB
    A1["ABIS 1"]:::core
    A2["ABIS 2"]:::core
    A3["ABIS 3"]:::core
    CON{"3-way consensus"}:::hot
    A1 & A2 & A3 --> CON
  end
  class RES client
  class EA,RG service
DiagramAadhaar architecture — authentication ecosystem
%% title: Aadhaar architecture — authentication ecosystem
flowchart TD
  RES2@{ icon: "sd:user", label: "Resident (at bank, telecom)" } --> SPD@{ icon: "sd:fingerprint", label: "Service Point Device" }
  SPD --> AUA@{ icon: "sd:building", label: "AUA (Auth User Agency)" }
  AUA ==>|"Encrypted PID"| ASA["ASA (Auth Service Agency)"]:::edge
  ASA ==>|"Secure leased line"| CID["CIDR (1:1 matching)<br/>Returns: Yes/No<br/>(no PII returned)"]:::core
  class RES2 client
  class SPD edge
  class AUA external

Key Components#

Component breakdown
1. Enrollment client
  • Runs on laptops in villages
  • Captures biometrics (certified devices only)
  • Works OFFLINE (syncs when connected)
  • Encrypts everything at capture time
  • Operator + Supervisor biometric signatures
2. Registrar
  • State governments, banks, oil companies
  • Responsible for enrollment agencies
  • First-level quality checks
  • Uploads packets to CIDR
3. CIDR (Central Identities Data Repository)
  • The "crown jewels" — all biometric data
  • Two data centers (Hebbal, Bengaluru + Manesar, Gurugram)
  • Active-active configuration (in this design)
  • NEVER exposed to internet directly
  • Only UIDAI has access
4. ABIS (Automated Biometric Identification System)
  • Three independent vendors (TCS+Neurotechnology, etc.)
  • Aadhaar splits the work among them by throughput; suspected duplicates are cross-checked by a second ABIS
  • This design: each runs complete deduplication, with a consensus decision
  • Vendor-neutral API integration
5. AUA (Authentication User Agency)
  • Banks, telecom, insurance companies
  • Licensed to use authentication
  • Must follow UIDAI security guidelines
  • Audited regularly
6. ASA (Authentication Service Agency)
  • Secure network intermediary
  • Connects AUAs to CIDR
  • Dedicated leased lines (not internet)
  • About 35 licensed ASAs in India (2026)

Data Flow: Enrollment#

UIDAI publishes the outline: an offline-capable client, packets encrypted at capture with an HMAC, uploaded by SFTP, deduplicated at CIDR. The hop-by-hop steps below are a likely design.

DiagramEnrollment flow
%% title: Enrollment flow
flowchart TD
  RES@{ icon: "sd:user", label: "Resident visits enrollment center with documents" }
  subgraph ST1["Step 1 · CAPTURE"]
    ES@{ icon: "sd:laptop", label: "Enrollment Station<br/>(Offline-capable laptop)" }
  end
  subgraph ST2["Step 2 · UPLOAD"]
    RB["Registrar Backend<br/>validates structure + signatures"]:::service
    RQ@{ icon: "sd:queue", label: "Queue for CIDR upload" }
    RB --> RQ
  end
  subgraph ST3["Step 3 · DEDUPLICATION · 3-90 days"]
    CP["CIDR Processing<br/>decrypt · extract templates<br/>demographic pre-filter<br/>send to all ABIS in parallel"]:::core
    B1["ABIS 1"]:::service
    B2["ABIS 2"]:::service
    B3["ABIS 3"]:::service
    CN{"Consensus:<br/>2 of 3 must agree"}:::hot
    REJ(["Reject · points to existing Aadhaar"]):::bad
    MA["Manual adjudication"]:::queue
    GEN["Generate Aadhaar number"]:::ok
    DB@{ icon: "sd:database", label: "Store in database" }
    PQ@{ icon: "sd:printer", label: "Queue letter for printing" }
    CP --> B1 & B2 & B3
    B1 & B2 & B3 --> CN
    CN -->|"DUPLICATE"| REJ
    CN -->|"No consensus"| MA
    MA -.->|"cleared"| GEN
    CN ==>|"UNIQUE"| GEN
    GEN --> DB --> PQ
  end
  subgraph ST4["Step 4 · DELIVERY"]
    LET@{ icon: "sd:mail", label: "Aadhaar Letter printed and mailed<br/>12-digit Aadhaar number + QR code" }
  end
  RES --> ES
  ES ==>|"Encrypted enrollment packet (3-5 MB)<br/>HMAC for tamper detection<br/>via SFTP or encrypted USB"| RB
  RQ ==>|"secure channel"| CP
  PQ --> LET
  class RES client
  class ES service
  class RQ,PQ queue
  class DB store
  class LET ok

Step 1: CAPTURE — Enrollment Station (offline-capable laptop)

  1. Operator logs in with biometric
  2. Captures resident's demographics
  3. Scans proof documents (Ration card, Voter ID, etc.)
  4. Captures 10 fingerprints (slaps + thumbs)
  5. Captures 2 iris scans
  6. Captures photograph
  7. Resident reviews and confirms
  8. Operator signs packet biometrically
  9. Supervisor approves (for exceptions)

Output: Encrypted enrollment packet (3-5 MB), contains HMAC for tamper detection.

Step 2: UPLOAD — Registrar Backend

  1. Receives packets via SFTP or encrypted USB
  2. Validates packet structure and signatures
  3. Queues for CIDR upload
  4. Uploads via secure channel to CIDR

Step 3: DEDUPLICATION — CIDR Processing

  1. Decrypt packet (only CIDR can decrypt)
  2. Extract biometric templates
  3. Demographic pre-filter (reduce search space)
  4. Send to ABIS 1, ABIS 2, ABIS 3 in parallel
  5. Each ABIS returns: UNIQUE / DUPLICATE / MANUAL_REVIEW
  6. Consensus: 2 of 3 must agree
  7. If DUPLICATE: Reject (linked to the existing Aadhaar); if no consensus: manual adjudication
  8. If UNIQUE: Generate Aadhaar number
  9. Store in database
  10. Queue letter for printing

Timeline: 3-90 days (depending on duplicates)

(This flow sends every packet to all three ABIS for a 2-of-3 vote. Aadhaar itself splits the load across its three vendors and sends suspected duplicates and poor-quality enrollments to more than one ABIS.)

Step 4: DELIVERY — Aadhaar Letter printed and mailed to resident's address. Contains: 12-digit Aadhaar number + QR code.

Quick check3 questions · +5 XP each

High-level architecture

  1. Q1/3 Enrollment packets travel from village laptops through registrars, sometimes on USB drives. Why are they encrypted at capture time?

    Answer & explanation

    Answer: B

    Encrypting on the device means every hop after capture, including registrars and USB transport, carries data only CIDR can decrypt, and the HMAC exposes tampering. Registrars validate packet structure and signatures; they do not need to read the biometrics.

  2. Q2/3 A bank authenticates a customer's fingerprint through Aadhaar. What does the bank get back?

    Answer & explanation

    Answer: D

    CIDR does the 1:1 match itself and returns only Yes/No. Returning templates or scores would leak data through every AUA. Demographic data is returned only through e-KYC, with consent, not in plain authentication.

  3. Q3/3 Why do AUAs reach CIDR through ASAs over leased lines instead of calling CIDR directly?

    Answer & explanation

    Answer: A

    ASAs are a small set of licensed network intermediaries on dedicated lines, which keeps the crown-jewel database off the public internet. Matching happens only inside CIDR; ASAs just carry the encrypted request.

Phase 4 · Stage 4 of 7 · 21 min

Phase 4: Deep Dives#

Deep Dive 1: Biometric Deduplication at Billion Scale#

Week 1 concepts: Partitioning, sharding. Week 3 concepts: Async processing.

You

"Deduplication is the hardest problem in Aadhaar. You must compare each new person against 1.4 billion existing records."

Your turn · checkpoint 3not answered

Why not just filter by name and birth date?

Splitting candidates by gender, age band, state and a phonetic name hash would shrink each search about 72,000-fold. Why is that not enough to guarantee that nobody enrols twice?

Think it through, or jot it down. Then compare.

One strong answer

Because the person trying to enrol twice chooses their demographics. Someone after a second identity simply gives a different name, birth date or state, and a demographic filter would never compare them with their first record. Blocking only catches careless duplicates.

So the biometric search has to cover the full gallery, made feasible with biometric indexing and massively parallel matching. Fusing fingerprints with iris multiplies independent errors down; three independent ABIS vendors must reach a 2-of-3 consensus; and borderline cases go to human adjudication.

WorksheetThe deduplication challenge

Without optimization:

  • New enrollment: 1 person
  • Existing database: 1,400,000,000 people
  • Fingers per person: 10
  • Iris per person: 2
  • Fingerprint comparisons: 10 (new) × 10 (existing) × 1.4B = 140 billion comparisons
  • At 1 million matches/second: 140,000 seconds = about 1.6 days for one enrollment
  • For 1 million enrollments/day: 1.4 × 10^17 comparisons, or about 4,400 years of compute per day of enrollment

This is impossible. We need smarter approaches.

How Aadhaar Makes It Tractable:

DEDUPLICATION OPTIMIZATION STRATEGIES

LAYER 1: DEMOGRAPHIC BLOCKING — a common textbook optimisation, with a big caveat: it only catches careless duplicates, because a fraudster can simply change name, DOB or state. Aadhaar's ABIS therefore searches biometrically against the full gallery (~1M enrollments/day × ~600M records ≈ 600 trillion matches/day at peak) and relies on biometric indexing plus massive parallel hardware. As a textbook blocking scheme, partition by:

  • Gender (2 partitions)
  • Age range (10-year buckets = 10 partitions)
  • State (36 partitions)
  • Name phonetic hash (100 partitions)

Effective reduction: 2 × 10 × 36 × 100 = 72,000x smaller search. 1.4B / 72,000 = 19,444 candidate matches per enrollment (vs 1.4 billion without blocking).

LAYER 2: MULTI-MODAL FUSION — Fingerprint (10 fingers) + Iris (2 eyes) combined:

WorksheetFusion score
  • Fingerprint score (0-100) × weight + Iris score (0-100) × weight = Final fusion score

Using both modalities:

  • Handles worn fingerprints (use iris)
  • Handles cataracts (use fingerprint)
  • Much higher accuracy than either alone

LAYER 3: MULTI-ABIS CROSS-CHECK — Aadhaar splits the work across three independent vendors and sends a suspected duplicate to a second ABIS to verify. This design goes further and asks all three for a 2-of-3 vote:

DiagramThree-way ABIS consensus
%% title: Three-way ABIS consensus
flowchart TD
  A1["ABIS 1 (Vendor A)"]:::service
  A2["ABIS 2 (Vendor B)"]:::service
  A3["ABIS 3 (Vendor C)"]:::service
  CE{"Consensus Engine"}:::core
  A1 & A2 & A3 --> CE
  CE -->|"3/3 UNIQUE"| ACC(["Accept enrollment"]):::ok
  CE -->|"3/3 DUPLICATE"| REJ(["Reject enrollment"]):::bad
  CE -->|"2/3 agree"| MAJ(["Follow majority"]):::service
  CE -->|"Mixed/uncertain"| MAN(["Manual adjudication"]):::queue

Why three vendors?

  • Different algorithms catch different edge cases
  • No single vendor lock-in
  • Higher accuracy through consensus
  • Continuous quality competition
pythonDeduplicationResult · 214 lines
# deduplication/abis_orchestrator.py

"""
ABIS (Automated Biometric Identification System) Orchestration

Aadhaar uses three independent ABIS vendors for deduplication.
This provides redundancy, accuracy, and vendor neutrality.
"""

from dataclasses import dataclass
from typing import List, Optional, Tuple
from enum import Enum
import asyncio


class DeduplicationResult(Enum):
    UNIQUE = "unique"           # No duplicates found
    DUPLICATE = "duplicate"     # Duplicate found
    MANUAL_REVIEW = "review"    # Uncertain, needs human review


@dataclass
class ABISMatch:
    candidate_aadhaar: str
    fingerprint_score: float      # 0-100
    iris_score: float             # 0-100
    fusion_score: float           # Combined score
    confidence: str               # HIGH, MEDIUM, LOW


@dataclass
class ABISResponse:
    vendor_id: str
    result: DeduplicationResult
    matches: List[ABISMatch]
    processing_time_ms: int


class ABISOrchestrator:
    """
    Orchestrates deduplication across three ABIS vendors.
    
    This design's multi-ABIS approach (Aadhaar itself shares the load
    across vendors and cross-checks suspected duplicates):
    1. Send enrollment to all three vendors in parallel
    2. Each vendor searches against their copy of the database
    3. Consensus determines final result
    """
    
    def __init__(
        self,
        abis_clients: List,  # Three ABIS vendor clients
        demographic_filter,
        manual_review_queue
    ):
        self.abis_clients = abis_clients
        self.demo_filter = demographic_filter
        self.review_queue = manual_review_queue
        
        # Thresholds for decision
        self.duplicate_threshold = 80  # Fusion score > 80 = duplicate
        self.unique_threshold = 30     # Fusion score < 30 = unique
    
    async def deduplicate(
        self,
        enrollment_packet: dict
    ) -> Tuple[DeduplicationResult, Optional[str]]:
        """
        Main deduplication flow.
        
        Returns:
            (result, duplicate_aadhaar if found)
        """
        # Step 1: Demographic blocking to reduce search space
        candidate_pool = await self.demo_filter.get_candidates(
            gender=enrollment_packet['gender'],
            dob=enrollment_packet['dob'],
            state=enrollment_packet['state'],
            name_phonetic=enrollment_packet['name_phonetic']
        )
        
        # Log the reduction achieved
        reduction_ratio = 1_400_000_000 / len(candidate_pool)
        print(f"Demographic blocking: {len(candidate_pool):,} candidates")
        print(f"Search space reduced by {reduction_ratio:,.0f}x")
        
        # Step 2: Send to all three ABIS in parallel
        abis_tasks = [
            client.search(
                fingerprints=enrollment_packet['fingerprints'],
                irises=enrollment_packet['irises'],
                candidate_pool=candidate_pool
            )
            for client in self.abis_clients
        ]
        
        responses: List[ABISResponse] = await asyncio.gather(*abis_tasks)
        
        # Step 3: Consensus decision
        return self._consensus_decision(responses)
    
    def _consensus_decision(
        self,
        responses: List[ABISResponse]
    ) -> Tuple[DeduplicationResult, Optional[str]]:
        """
        Three-way consensus logic.
        
        This design requires 2/3 agreement for automated decision.
        Mixed results go to manual adjudication.
        """
        unique_count = sum(
            1 for r in responses if r.result == DeduplicationResult.UNIQUE
        )
        duplicate_count = sum(
            1 for r in responses if r.result == DeduplicationResult.DUPLICATE
        )
        
        # Case 1: All three agree UNIQUE
        if unique_count == 3:
            return (DeduplicationResult.UNIQUE, None)
        
        # Case 2: All three agree DUPLICATE
        if duplicate_count == 3:
            # Find the matching Aadhaar (should be same across all)
            duplicate_aadhaar = self._find_common_match(responses)
            return (DeduplicationResult.DUPLICATE, duplicate_aadhaar)
        
        # Case 3: 2/3 agree UNIQUE
        if unique_count >= 2:
            return (DeduplicationResult.UNIQUE, None)
        
        # Case 4: 2/3 agree DUPLICATE
        if duplicate_count >= 2:
            duplicate_aadhaar = self._find_common_match(responses)
            return (DeduplicationResult.DUPLICATE, duplicate_aadhaar)
        
        # Case 5: No consensus — manual review
        # Queue for human adjudicator
        return (DeduplicationResult.MANUAL_REVIEW, None)
    
    def _find_common_match(
        self,
        responses: List[ABISResponse]
    ) -> Optional[str]:
        """
        Find the Aadhaar number that multiple ABIS agree is a duplicate.
        """
        # Collect all top matches
        match_counts = {}
        for response in responses:
            if response.matches:
                top_match = response.matches[0]
                aadhaar = top_match.candidate_aadhaar
                match_counts[aadhaar] = match_counts.get(aadhaar, 0) + 1
        
        # Return the one with most agreement
        if match_counts:
            return max(match_counts, key=match_counts.get)
        return None


class DemographicBlockingFilter:
    """
    Pre-filters the biometric search space using demographics.
    
    This is crucial for making billion-scale deduplication tractable.
    """
    
    def __init__(self, database):
        self.db = database
    
    async def get_candidates(
        self,
        gender: str,
        dob: str,
        state: str,
        name_phonetic: str
    ) -> List[str]:
        """
        Get candidate Aadhaar numbers that match demographic criteria.
        
        Reduces 1.4 billion to ~20,000-50,000 candidates.
        """
        # Parse DOB to get age range
        birth_year = int(dob[:4])
        age_range_start = birth_year - 5
        age_range_end = birth_year + 5
        
        # Query with demographic filters
        candidates = await self.db.query("""
            SELECT aadhaar_number 
            FROM residents
            WHERE gender = ?
              AND birth_year BETWEEN ? AND ?
              AND state_code = ?
              AND name_phonetic_hash = ?
        """, [gender, age_range_start, age_range_end, 
              state, self._phonetic_hash(name_phonetic)])
        
        return [row['aadhaar_number'] for row in candidates]
    
    def _phonetic_hash(self, name: str) -> str:
        """
        Generate phonetic hash for name matching.
        
        Handles spelling variations:
        "Rahul" and "Rahool" → same hash
        "Priya" and "Priyaa" → same hash
        """
        # Simplified phonetic algorithm (a real system would use a Soundex-style one)
        # Remove vowels, normalize consonants
        consonants = ''.join(c for c in name.upper() if c not in 'AEIOU')
        return consonants[:4].ljust(4, '0')

Deep Dive 2: Authentication in 200 Milliseconds#

Week 2 concepts: Timeouts, latency budgets. Week 4 concepts: Caching.

You

"Authentication must verify identity in under 200ms. This is 1:1 matching — much simpler than deduplication, but still demanding at scale."

Your turn · checkpoint 4not answered

Spend the 200 milliseconds

List the steps an authentication request goes through between the bank and the repository, and give each a share of the 200 ms budget. Which step gets the largest slice?

Think it through, or jot it down. Then compare.

One strong answer

  • Network, agency → service agency → CIDR and back: ~50 ms.
  • Cryptography, decrypting the biometric block, verifying the signature, signing the response: ~30 ms.
  • Lookup of the record and its stored template: ~20 ms.
  • The 1:1 biometric match: ~80 ms, the largest slice.
  • Overhead: ~20 ms.

With the match taking the biggest share, everything around it has to be predictable: templates indexed by number, crypto in HSMs, no fan-out to other services, and strict per-hop timeouts.

WorksheetAuthentication latency budget

Total budget: 200ms

  • Network (AUA → ASA → CIDR → ASA → AUA): 50ms
    • AUA to ASA: 15ms
    • ASA to CIDR: 10ms
    • CIDR to ASA: 10ms
    • ASA to AUA: 15ms
  • Cryptographic operations: 30ms
    • Decrypt PID block (at CIDR): 10ms
    • Signature verification: 5ms
    • Sign response: 15ms
  • Database lookup: 20ms
    • Find Aadhaar record: 10ms
    • Load biometric template: 10ms
  • Biometric 1:1 matching: 80ms
    • Fingerprint match: 40ms
    • Iris match (if used): 40ms
  • Processing overhead: 20ms
  • Total: 200ms
ExplainerAadhaar authentication: spending the 200 ms

A bank (the AUA) sends the encrypted PID block via an ASA to CIDR (25 ms). CIDR's HSM decrypts it and checks the signature (15 ms), the record and template load (20 ms) and the 1:1 match takes the largest slice (80 ms); signing, the trip back and overhead (15 + 25 + 20 ms) complete the 200 ms. Only y or n returns, never biometric or personal data, and after an n the bank falls back to another finger, iris, OTP or face.

pythonAuthMode · 226 lines
# authentication/auth_service.py

"""
Aadhaar Authentication Service

Processes 90+ million authentications per day with <200ms latency.
Returns only Yes/No — never exposes biometric or demographic data.
"""

from dataclasses import dataclass
from typing import Optional
from enum import Enum
from datetime import datetime, timedelta
import asyncio


class AuthMode(Enum):
    DEMOGRAPHIC = "demo"           # Name/address matching
    OTP = "otp"                    # One-time password
    FINGERPRINT = "fmr"            # Fingerprint biometric
    IRIS = "iir"                   # Iris biometric
    FACE = "fid"                   # Face biometric
    MULTI_FACTOR = "mf"            # Combination


@dataclass
class AuthRequest:
    aadhaar_number: str            # Or VID (Virtual ID)
    auth_mode: AuthMode
    pid_block: bytes               # Encrypted biometric/OTP
    aua_code: str                  # Authentication User Agency
    timestamp: datetime
    transaction_id: str
    consent: bool                  # Must be True


@dataclass
class AuthResponse:
    transaction_id: str
    status: str                    # "y" (yes) or "n" (no)
    error_code: Optional[str]      # If status is "n"
    auth_token: Optional[str]      # Unique token for this auth
    timestamp: datetime
    # NOTE: No PII is ever returned!


class AadhaarAuthService:
    """
    Core authentication service running in CIDR.
    
    Security principles:
    1. All requests encrypted with 2048-bit PKI
    2. Biometric data decrypted only inside CIDR
    3. Response is only Yes/No (no data leakage)
    4. All transactions logged for audit
    """
    
    def __init__(
        self,
        biometric_db,
        biometric_matcher,
        otp_service,
        hsm_client,          # Hardware Security Module
        audit_logger
    ):
        self.db = biometric_db
        self.matcher = biometric_matcher
        self.otp = otp_service
        self.hsm = hsm_client
        self.audit = audit_logger
        
        # Performance tuning
        self.template_cache = {}  # LRU cache for hot Aadhaars
        self.cache_ttl = 300      # 5 minutes
    
    async def authenticate(
        self,
        request: AuthRequest
    ) -> AuthResponse:
        """
        Main authentication flow.
        
        Must complete in <200ms.
        """
        start_time = datetime.utcnow()
        
        try:
            # Step 1: Validate request format
            self._validate_request(request)
            
            # Step 2: Decrypt PID block using HSM
            # Only CIDR's HSM can decrypt
            pid_data = await self.hsm.decrypt_pid(request.pid_block)
            
            # Step 3: Resolve Aadhaar number (handle VID)
            aadhaar = await self._resolve_aadhaar(request.aadhaar_number)
            
            # Step 4: Load resident's template (with caching)
            resident_template = await self._load_template(aadhaar)
            
            # Step 5: Perform matching based on auth mode
            match_result = await self._perform_match(
                mode=request.auth_mode,
                pid_data=pid_data,
                stored_template=resident_template
            )
            
            # Step 6: Generate response
            response = AuthResponse(
                transaction_id=request.transaction_id,
                status="y" if match_result.success else "n",
                error_code=match_result.error_code,
                auth_token=self._generate_token(aadhaar, request.aua_code),
                timestamp=datetime.utcnow()
            )
            
            # Step 7: Audit logging (async, don't wait)
            asyncio.create_task(self.audit.log(
                aadhaar=aadhaar,
                aua_code=request.aua_code,
                auth_mode=request.auth_mode,
                result=response.status,
                latency_ms=(datetime.utcnow() - start_time).total_seconds() * 1000
            ))
            
            return response
            
        except ValidationError as e:
            return self._error_response(request, str(e))
        except Exception as e:
            # Never expose internal errors
            return self._error_response(request, "INTERNAL_ERROR")
    
    async def _load_template(self, aadhaar: str) -> dict:
        """
        Load biometric template with caching.
        
        Hot Aadhaars (frequently authenticated) are cached.
        """
        # Check cache first
        if aadhaar in self.template_cache:
            cached = self.template_cache[aadhaar]
            if cached['expires'] > datetime.utcnow():
                return cached['template']
        
        # Cache miss — load from database
        template = await self.db.get_template(aadhaar)
        
        # Cache for hot Aadhaars
        self.template_cache[aadhaar] = {
            'template': template,
            'expires': datetime.utcnow() + timedelta(seconds=self.cache_ttl)
        }
        
        return template
    
    async def _perform_match(
        self,
        mode: AuthMode,
        pid_data: dict,
        stored_template: dict
    ) -> MatchResult:
        """
        Perform biometric/demographic/OTP matching.
        """
        if mode == AuthMode.FINGERPRINT:
            # 1:1 fingerprint matching
            return await self.matcher.match_fingerprint(
                captured=pid_data['fingerprint'],
                stored=stored_template['fingerprints'],
                finger_position=pid_data.get('position', 'ANY')
            )
        
        elif mode == AuthMode.IRIS:
            # 1:1 iris matching
            return await self.matcher.match_iris(
                captured=pid_data['iris'],
                stored=stored_template['irises']
            )
        
        elif mode == AuthMode.FACE:
            # AI-powered face matching
            return await self.matcher.match_face(
                captured=pid_data['face_image'],
                stored=stored_template['photo'],
                liveness_check=pid_data.get('liveness_data')
            )
        
        elif mode == AuthMode.OTP:
            # Verify OTP sent to registered mobile
            return await self.otp.verify(
                aadhaar=stored_template['aadhaar'],
                submitted_otp=pid_data['otp']
            )
        
        elif mode == AuthMode.DEMOGRAPHIC:
            # Fuzzy matching on name/address
            return self._demographic_match(
                submitted=pid_data['demographics'],
                stored=stored_template['demographics']
            )
    
    def _demographic_match(self, submitted: dict, stored: dict) -> MatchResult:
        """
        Fuzzy matching for name and address.
        
        Handles variations like:
        - "Raj Kumar" vs "Rajkumar"  
        - "Bangalore" vs "Bengaluru"
        - Hindi transliterations
        """
        name_score = self._fuzzy_name_match(
            submitted.get('name', ''),
            stored['name']
        )
        
        address_score = self._fuzzy_address_match(
            submitted.get('address', ''),
            stored['address']
        )
        
        # Thresholds from UIDAI guidelines
        if name_score >= submitted.get('match_threshold', 100):
            return MatchResult(success=True)
        
        return MatchResult(success=False, error_code="DEMO_MISMATCH")
Quick check3 questions · +5 XP each

Deduplication and authentication

  1. Q1/3 Demographic blocking uses gender (2) x age bucket (10) x state (36) x phonetic hash (100). If you dropped the state partition, roughly how many candidates would each enrollment be matched against?

    Answer & explanation

    Answer: B

    Without state the reduction is 2 x 10 x 100 = 2,000x, so 1.4B / 2,000 is about 700,000 candidates, versus about 19,444 with all four partitions. 19,000 is the figure with state included; each partition you drop multiplies the matching work.

  2. Q2/3 For one enrollment, ABIS 1 says UNIQUE, ABIS 2 says DUPLICATE and ABIS 3 says MANUAL_REVIEW. What does the orchestrator decide?

    Answer & explanation

    Answer: D

    Automated decisions need at least 2 of 3 to agree; here every vendor says something different, so it goes to human adjudication. No single vendor's verdict decides on its own, which is the point of running three independent algorithms.

  3. Q3/3 The auth service fires its audit log write with asyncio.create_task instead of awaiting it. Why?

    Answer & explanation

    Answer: B

    Every authentication is still logged, but the response does not wait for the write, protecting the tight latency budget. Skipping or dropping logs is not the goal; residents can see their authentication history, so logging must happen.

Deep Dive 3: Security Architecture — Protecting Billion Biometrics#

Week 9 concepts: Security, encryption, zero-trust.

You

"Aadhaar stores the biometrics of 1.4 billion people. This is the ultimate honeypot. Security is existential."

Aadhaar security layers
Layer 1: Encryption everywhere

At capture:

  • Biometrics encrypted on enrollment device
  • 2048-bit PKI encryption
  • Only CIDR has private key to decrypt

In transit:

  • All communication over encrypted channels
  • TLS 1.2+ mandatory
  • Dedicated leased lines (not public internet)

At rest:

  • AES-256 encryption for stored data
  • Even within CIDR, data is encrypted
  • Keys stored in HSM (Hardware Security Module)
Layer 2: Tamper detection

Every enrollment packet includes:

  • HMAC for integrity verification
  • Operator's biometric signature
  • Supervisor's biometric signature (for exceptions)
  • GPS coordinates of enrollment station
  • Timestamp
  • Device ID

Any tampering is detectable and traceable

Layer 3: Access control

CIDR access:

  • Only UIDAI employees (very few)
  • Multi-factor authentication required
  • All access logged and audited
  • No external access to raw biometrics

Partner access (AUA/ASA):

  • Can only call authentication API
  • Cannot query or download data
  • Rate limited per entity
  • Licensed and audited
Layer 4: Response minimization

Authentication returns ONLY:

  • Yes (match) or No (no match)
  • Transaction ID
  • Timestamp

NEVER returns:

  • Biometric data
  • Demographic data (unless e-KYC with consent)
  • Match scores
  • Reason for failure (in detail)

This prevents information leakage

Privacy Features Added Over Time:

Privacy enhancements
1. Virtual ID (VID) — Introduced 2018
  • Problem: Every authentication exposes Aadhaar number
  • Solution: 16-digit temporary ID that maps to Aadhaar

Resident generates VID → Uses VID instead of Aadhaar. Each VID is:

  • Revocable (generate new anytime)
  • Mappable only by CIDR
  • Usable for authentication

AUA never sees actual Aadhaar number

2. Tokenization — For recurring services
  • Problem: Same Aadhaar used at multiple services; services could collude to track user
  • Solution: Each AUA gets a unique token for each Aadhaar

Aadhaar 1234-5678-9012 →

  • Bank A: Token ABC123
  • Telecom B: Token XYZ789
  • Insurance C: Token PQR456

Services cannot correlate tokens

3. Masked Aadhaar
  • Display: XXXX-XXXX-9012
  • Only last 4 digits visible
  • Used for documents that need to show Aadhaar reference
4. Authentication history

Resident can see:

  • Who authenticated their Aadhaar
  • When
  • What type of authentication

Provides transparency and detects misuse

pythonPIDBlockEncryption · 189 lines
# security/encryption_service.py

"""
Aadhaar Encryption Service

All biometric data is encrypted at the point of capture.
Only CIDR can decrypt using HSM-protected private keys.
"""

from dataclasses import dataclass
from typing import Tuple
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import hmac
import hashlib
import os


class PIDBlockEncryption:
    """
    PID (Personal Identity Data) Block encryption.
    
    The PID block contains biometric/OTP data captured at
    the authentication point. It's encrypted before transmission.
    
    Encryption scheme (illustrative; UIDAI's API spec wraps the
    session key with RSA/ECB/PKCS1Padding, this sketch uses OAEP):
    1. Generate random session key (AES-256)
    2. Encrypt biometric data with session key
    3. Encrypt session key with UIDAI's public key (RSA-2048)
    4. Add HMAC for integrity
    """
    
    def __init__(self, uidai_public_key: bytes):
        self.uidai_public_key = serialization.load_pem_public_key(
            uidai_public_key
        )
    
    def encrypt_pid(
        self,
        biometric_data: bytes,
        timestamp: str,
        device_id: str
    ) -> Tuple[bytes, bytes]:
        """
        Encrypt PID block for transmission to CIDR.
        
        Returns:
            (encrypted_data, encrypted_session_key)
        """
        # Generate random session key
        session_key = os.urandom(32)  # 256 bits
        iv = os.urandom(12)  # 96 bits for GCM
        
        # Create PID plaintext with metadata
        pid_plaintext = self._create_pid_xml(
            biometric_data=biometric_data,
            timestamp=timestamp,
            device_id=device_id
        )
        
        # Encrypt with AES-256-GCM
        cipher = Cipher(algorithms.AES(session_key), modes.GCM(iv))
        encryptor = cipher.encryptor()
        ciphertext = encryptor.update(pid_plaintext) + encryptor.finalize()
        
        # Encrypt session key with UIDAI's RSA public key
        encrypted_session_key = self.uidai_public_key.encrypt(
            session_key + iv,  # Include IV
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
        
        # Add HMAC for integrity
        hmac_value = hmac.new(
            session_key,
            ciphertext,
            hashlib.sha256
        ).digest()
        
        return (ciphertext + encryptor.tag + hmac_value, 
                encrypted_session_key)


class HSMKeyManager:
    """
    Hardware Security Module interface.
    
    All cryptographic keys are stored and used within HSM.
    Keys never leave the HSM in plaintext.
    
    UIDAI requires FIPS 140-2 Level 3 (or higher) HSMs for partner
    agencies' keys; assume the same class here.
    """
    
    def __init__(self, hsm_connection):
        self.hsm = hsm_connection
    
    async def decrypt_pid(self, encrypted_pid: bytes, 
                          encrypted_key: bytes) -> dict:
        """
        Decrypt PID block inside HSM.
        
        The private key never leaves the HSM.
        Decryption happens entirely within the HSM.
        """
        # Send to HSM for decryption
        decrypted = await self.hsm.decrypt(
            data=encrypted_pid,
            encrypted_key=encrypted_key,
            key_id="UIDAI_AUTH_PRIVATE_KEY",
            algorithm="RSA-OAEP-256"
        )
        
        return self._parse_pid_xml(decrypted)
    
    async def sign_response(self, response_data: bytes) -> bytes:
        """
        Sign authentication response using HSM.
        
        All responses are digitally signed so AUAs can
        verify they came from authentic CIDR.
        """
        signature = await self.hsm.sign(
            data=response_data,
            key_id="UIDAI_SIGNING_KEY",
            algorithm="RSA-SHA256"
        )
        
        return signature


class AadhaarDataVault:
    """
    Secure storage for Aadhaar numbers at AUA/KUA.
    
    UIDAI mandates that any entity storing Aadhaar numbers
    must use an "Aadhaar Data Vault" with:
    - AES-256 encryption
    - Keys in HSM
    - Access logging
    - No plaintext storage
    """
    
    def __init__(self, hsm_client, database):
        self.hsm = hsm_client
        self.db = database
    
    async def store_aadhaar(
        self,
        reference_id: str,  # Your internal customer ID
        aadhaar_number: str
    ):
        """
        Store Aadhaar number securely.
        """
        # Generate reference key in HSM
        encrypted_aadhaar = await self.hsm.encrypt(
            data=aadhaar_number.encode(),
            key_id="AADHAAR_VAULT_KEY"
        )
        
        # Store only encrypted value
        await self.db.insert({
            'reference_id': reference_id,
            'encrypted_aadhaar': encrypted_aadhaar,
            'created_at': datetime.utcnow()
        })
    
    async def get_aadhaar(self, reference_id: str) -> str:
        """
        Retrieve and decrypt Aadhaar number.
        """
        record = await self.db.get(reference_id)
        
        # Decrypt in HSM
        aadhaar = await self.hsm.decrypt(
            data=record['encrypted_aadhaar'],
            key_id="AADHAAR_VAULT_KEY"
        )
        
        # Log access
        await self._log_access(reference_id)
        
        return aadhaar.decode()

Deep Dive 4: Face Authentication — AI at Scale#

Week 10 concepts: Operational excellence, innovation.

You

"Face authentication was introduced in 2021 and has exploded to about 28 crore (280 million) transactions per month by late 2025, and 500+ crore in total by September 2026. It's AI-powered and developed in-house by UIDAI."

Face authentication evolution
Why face authentication?

Problems with fingerprint:

  • Manual laborers: Worn fingerprints
  • Elderly: Faded fingerprints
  • Amputees: Missing fingers
  • Skin conditions: Temporary issues
  • COVID-19: Hygiene concerns with touch-based

Face authentication advantages:

  • Contactless (post-COVID preference)
  • Works on any smartphone
  • More inclusive (no physical requirements)
  • Convenient for remote authentication
Growth trajectory
  • Oct 2021 — Launch
  • Jan 2025 — 100 crore (1 billion) cumulative
  • Jul 2025 — 19.4 crore (194 million) in one month
  • Aug 2025 — 200 crore (2 billion) cumulative
  • Sep 2025 — 1.5 crore (15 million) per DAY (record)
  • Nov 2025 — 28.3 crore (283 million) in one month
  • Sep 2026 — 500+ crore (5 billion) cumulative

Adoption: nearly 200 government and private entities (September 2026)

pythonFaceAuthRequest · 221 lines
# authentication/face_auth.py

"""
UIDAI Face Authentication Service

AI/ML-powered face matching developed in-house by UIDAI.
Uses liveness detection to prevent spoofing.
"""

from dataclasses import dataclass
from typing import Tuple
import numpy as np


@dataclass
class FaceAuthRequest:
    aadhaar_or_vid: str
    face_image: bytes              # Captured selfie
    liveness_data: dict            # Blink detection, head movement
    device_info: dict              # Camera specs, OS version


@dataclass
class FaceAuthResult:
    match: bool
    confidence: float              # 0-100
    liveness_verified: bool
    error_code: str = None


class FaceAuthenticationService:
    """
    Face authentication with liveness detection.
    
    Architecture:
    1. Liveness detection (prevent photo/video attacks)
    2. Face detection and alignment
    3. Feature extraction (deep learning)
    4. 1:1 matching against stored photo
    """
    
    def __init__(
        self,
        face_detector,
        liveness_model,
        face_encoder,
        match_threshold: float = 0.85
    ):
        self.detector = face_detector
        self.liveness = liveness_model
        self.encoder = face_encoder
        self.threshold = match_threshold
    
    async def authenticate(
        self,
        request: FaceAuthRequest,
        stored_photo: bytes
    ) -> FaceAuthResult:
        """
        Perform face authentication.
        
        Steps:
        1. Verify liveness (not a photo/video)
        2. Detect and align faces
        3. Extract embeddings
        4. Compare embeddings
        """
        # Step 1: Liveness verification
        liveness_result = await self._verify_liveness(
            image=request.face_image,
            liveness_data=request.liveness_data
        )
        
        if not liveness_result.is_live:
            return FaceAuthResult(
                match=False,
                confidence=0,
                liveness_verified=False,
                error_code="LIVENESS_FAILED"
            )
        
        # Step 2: Face detection and quality check
        captured_face = await self._detect_and_align(request.face_image)
        stored_face = await self._detect_and_align(stored_photo)
        
        if captured_face is None:
            return FaceAuthResult(
                match=False,
                confidence=0,
                liveness_verified=True,
                error_code="FACE_NOT_DETECTED"
            )
        
        # Step 3: Extract face embeddings
        captured_embedding = await self.encoder.encode(captured_face)
        stored_embedding = await self.encoder.encode(stored_face)
        
        # Step 4: Compare embeddings
        similarity = self._cosine_similarity(
            captured_embedding,
            stored_embedding
        )
        
        # Convert to percentage
        confidence = (similarity + 1) / 2 * 100
        
        return FaceAuthResult(
            match=confidence >= self.threshold * 100,
            confidence=confidence,
            liveness_verified=True
        )
    
    async def _verify_liveness(
        self,
        image: bytes,
        liveness_data: dict
    ) -> LivenessResult:
        """
        Verify the face is from a live person.
        
        Liveness checks:
        1. Texture analysis (detect printed photos)
        2. Depth estimation (detect flat screens)
        3. Eye blink detection (detect videos)
        4. Random challenge-response (head movement)
        """
        # Challenge: User was asked to blink/move head
        challenge_type = liveness_data.get('challenge_type')
        frames = liveness_data.get('frames', [])
        
        if challenge_type == 'BLINK':
            # Detect eye blink across frames
            return await self.liveness.detect_blink(frames)
        
        elif challenge_type == 'HEAD_TURN':
            # Detect head movement
            return await self.liveness.detect_head_movement(frames)
        
        elif challenge_type == 'PASSIVE':
            # Passive liveness (texture + depth analysis)
            return await self.liveness.passive_check(image)
        
        return LivenessResult(is_live=False)
    
    def _cosine_similarity(
        self,
        embedding1: np.ndarray,
        embedding2: np.ndarray
    ) -> float:
        """
        Cosine similarity between face embeddings.
        
        Range: -1 to 1 (higher = more similar)
        """
        dot_product = np.dot(embedding1, embedding2)
        norm1 = np.linalg.norm(embedding1)
        norm2 = np.linalg.norm(embedding2)
        
        return dot_product / (norm1 * norm2)


class LivenessDetector:
    """
    Deep learning model for liveness detection.
    
    Trained to distinguish:
    - Real faces
    - Printed photos
    - Screen displays (replay attacks)
    - 3D masks
    """
    
    def __init__(self, model_path: str):
        self.model = self._load_model(model_path)
    
    async def passive_check(self, image: bytes) -> LivenessResult:
        """
        Passive liveness without user action.
        
        Analyzes:
        - Moiré patterns (screen artifacts)
        - Color distribution
        - Texture frequency
        - Specular reflection
        """
        features = self._extract_liveness_features(image)
        
        # Model predicts: real (1) vs spoof (0)
        prediction = self.model.predict(features)
        
        return LivenessResult(
            is_live=prediction > 0.7,
            confidence=prediction,
            method="PASSIVE"
        )
    
    async def detect_blink(self, frames: list) -> LivenessResult:
        """
        Detect eye blink across video frames.
        
        A real person blinks; a photo doesn't.
        """
        eye_aspect_ratios = []
        
        for frame in frames:
            # Detect eyes
            eyes = self._detect_eyes(frame)
            if eyes:
                ear = self._eye_aspect_ratio(eyes)
                eye_aspect_ratios.append(ear)
        
        # Blink = dip in eye aspect ratio
        if len(eye_aspect_ratios) > 5:
            min_ear = min(eye_aspect_ratios)
            max_ear = max(eye_aspect_ratios)
            
            # Significant dip indicates blink
            if (max_ear - min_ear) > 0.15:
                return LivenessResult(is_live=True, method="BLINK")
        
        return LivenessResult(is_live=False, method="BLINK")
Quick check3 questions · +5 XP each

Security and face authentication

  1. Q1/3 A resident uses the same Aadhaar at a bank, a telecom and an insurer. Why does each AUA get a different token for that Aadhaar?

    Answer & explanation

    Answer: A

    Per-AUA tokens stop services from colluding: a bank token and a telecom token for the same person do not correlate. AUAs never receive biometrics at all, so tokens are not about decryption.

  2. Q2/3 An encrypted PID block leaves a bank's fingerprint scanner. Where can it be decrypted?

    Answer & explanation

    Answer: C

    The session key is wrapped with UIDAI's public key, and only the matching private key, held inside CIDR's HSM, can unwrap it. A public key can only encrypt; it is published widely precisely because it cannot decrypt.

  3. Q3/3 Why does face authentication check liveness before comparing embeddings?

    Answer & explanation

    Answer: D

    A photo of the real person matches the stored photo, so the system must first prove a live face is in front of the camera. The stored enrollment photo is trusted; liveness is about the capture, not cost ordering.

Phase 5 · Stage 5 of 7 · 2 min

Phase 5: Scaling and Edge Cases#

Interviewer

"Aadhaar went from 0 to 1 billion enrollments in about 6 years. How did they scale the enrollment infrastructure?"

Enrollment at Village Scale#

Enrollment infrastructure
The challenge
  • 640,000+ villages in India
  • Many with no electricity, no internet
  • Extreme temperatures (deserts, mountains)
  • Low literacy levels
  • Need to enroll 1+ million people per day
The solution: mobile enrollment camps

Equipment transported to remote camps:

  • Ruggedized laptops
  • USB fingerprint scanners
  • Iris cameras
  • Web cameras (for photos)
  • Portable generators
  • Tables, chairs, canopies

Personnel:

  • 150,000+ certified operators
  • Supervisors for exception handling
  • ~50 enrollments per station per day

Peak capacity (circa 2014):

  • 60,000-80,000 enrollment stations
  • 1+ million new enrollments per day

Edge Cases#

Edge cases
Edge case 1: Biometric exceptions

Problem: Not everyone can provide all biometrics

  • Missing fingers (accidents, leprosy)
  • Cataract patients (can't capture iris)
  • Worn fingerprints (manual laborers)
  • Children (biometrics change as they grow)

Solution:

  • Multi-modal: If fingerprint fails, use iris
  • Best-finger approach: Use whichever fingers work
  • Exception handling: Supervisor approval for special cases
  • Child enrollment: Mandatory biometric update at 5 and 15 years
  • Biometric-exception enrollment (missing biometrics marked and photographed) for extreme cases
Edge case 2: Offline enrollment

Problem: No internet in remote villages

Solution:

  • Enrollment client works entirely offline
  • Packets stored locally (encrypted)
  • Sync when connectivity available
  • USB-based upload via registrar
  • 30-day buffer for packet upload
Edge case 3: Duplicate enrollment attempts

Problem: People trying to get multiple Aadhaars for fraud

Solution:

  • Three-way ABIS consensus
  • Manual adjudication for borderline cases
  • Reject if duplicate found
  • Audit trail for investigation
  • Criminal penalties for fraud
Edge case 4: Authentication failures

Problem: Genuine person fails authentication

Causes:

  • Worn fingerprints (temporary or permanent)
  • Cuts/injuries on fingers
  • Wet/dirty fingers
  • Sensor quality issues
  • Aging (biometrics change over time)

Solution:

  • Try multiple fingers
  • Fall back to iris
  • Fall back to OTP
  • Face authentication option
  • Biometric update facility
  • Exception handling mode for genuine failures
Edge case 5: System under attack

Problem: DDoS or brute-force attacks

Mitigation:

  • Rate limiting per AUA
  • IP whitelisting for ASAs
  • No direct internet exposure
  • Anomaly detection
  • Fallback to degraded mode
Quick check3 questions · +5 XP each

Enrollment scale and edge cases

  1. Q1/3 At about 50 enrollments per station per day, how many stations must be running to sustain 1 million enrollments a day?

    Answer & explanation

    Answer: B

    1,000,000 / 50 = 20,000 stations. The 60,000-80,000 peak stations give headroom for downtime, travel and slow days. 2,000 stations would manage only about 100,000 enrollments a day.

  2. Q2/3 An elderly laborer's worn fingerprints fail authentication at a ration shop. What is the right system behavior?

    Answer & explanation

    Answer: C

    The design assumes some genuine people cannot pass fingerprint checks, so it offers other fingers, iris, OTP and face rather than denying service. Lowering the global threshold would raise false accepts for everyone to fix a problem a fallback already solves.

  3. Q3/3 An enrollment camp has had no connectivity for three weeks. What happens to its packets?

    Answer & explanation

    Answer: D

    The client is offline-first: packets are stored encrypted, then synced or carried by USB when possible. Deduplication needs the full database, so it can only run at CIDR.

Phase 6 · Stage 6 of 7 · 1 min

Phase 6: Monitoring and Operations#

You

"With 90 million daily authentications, operational excellence is critical."

Monitoring dashboard (illustrative numbers)
Real-time metrics · Authentication rate
Metric Value
Current TPS 1,042 auth/sec
Today's Total 67.2M authentications
Success Rate 99.2%
p99 Latency 185ms
By authentication type
Type Share
Fingerprint 58%
OTP 22%
Face 12%
Iris 5%
Demographic 3%
Top AUAs (by volume)
AUA Authentications
SBI Bank 12.3M
NPCI/UPI 9.8M
Jio Telecom 7.2M
HDFC Bank 5.6M
Others 32.3M
Infrastructure health
Component Status Load
CIDR Bengaluru Healthy CPU: 45%
CIDR Manesar Healthy CPU: 42%
ABIS 1 Healthy Queue: 234
ABIS 2 Healthy Queue: 189
ABIS 3 Healthy Queue: 267
Quick check3 questions · +5 XP each

Operating the identity layer

  1. Q1/3 An authentication response carries only yes/no, a transaction ID and a timestamp, never the match score. Why not return the score?

    Answer & explanation

    Answer: B

    A numeric score tells an attacker whether each tweak brought them closer to a match (a hill-climbing attack). Returning only a decision minimises what any caller learns.

  2. Q2/3 The dashboard shows queue depths for each ABIS vendor. What does a growing ABIS queue mean?

    Answer & explanation

    Answer: B

    Deduplication runs asynchronously behind enrolment, so its backlog grows without touching the synchronous authentication path. That is exactly why it gets its own metric.

  3. Q3/3 How does a Virtual ID reduce the risk of a resident being tracked across services?

    Answer & explanation

    Answer: B

    With a VID, the agency never learns the actual number, and the resident can replace it at any time. Per-agency tokens add to this, so two services cannot join their records on a shared identifier.

Final challenge · Stage 7 of 7 · 2 min

Final Challenge: There Is No Password Reset for Fingerprints#

The interviewer sets down the marker. "Here's the scenario that keeps identity architects up at night."

Interviewer

"Investigators find fraud at a few hundred payment terminals: replayed biometric captures and fingerprints cloned in silicone, used to authenticate residents who were nowhere near. You can't reissue anyone's fingers. How does the system defend itself?"

The incident brief
  • Some attacks resend captures that were recorded earlier
  • Others present fake fingers made from copied prints
  • The terminals belong to licensed agencies and look legitimate
  • The residents affected never visited those terminals
Your turn · checkpoint 5not answered

Revoke what you can, strengthen what you can't

A fingerprint can't be rotated like a password. What can be revoked or tightened, and where would you detect each kind of attack?

Think it through, or jot it down. Then compare.

One strong answer

  • Stop replays at the sensor: only certified capture devices are accepted. Each one encrypts and signs the capture with its own device key, together with a timestamp and a unique transaction ID, so the server rejects stale or already-seen captures, and a compromised device's key can be revoked.
  • Stop fakes with liveness: liveness detection on fingerprint and face capture rejects silicone and photos, and high-value operations require a second factor such as an OTP.
  • Detect with the logs you already have: one device authenticating hundreds of different residents in an hour, or residents authenticating far from where they usually transact, is an anomaly to alert on and rate-limit per agency.
  • Give residents controls: let them lock biometric authentication until they need it, and notify them of every authentication so misuse is spotted quickly.
  • The principle: the biometric is a permanent identifier, not a secret. Security comes from what can be rotated (device keys, sessions, factors) wrapped around it.
Quick check4 questions · +5 XP each

Final challenge · Quest check

  1. Q1/4 Yesterday's encrypted biometric capture is resent today. What should make the server reject it?

    Answer & explanation

    Answer: B

    A replayed capture still contains a real fingerprint, so matching alone accepts it. Freshness and uniqueness, signed by a trusted device, are what turn a recording into a rejected request.

  2. Q2/4 Why can't a fingerprint leak be handled the way a password leak is?

    Answer & explanation

    Answer: B

    A password is a secret you can rotate. A biometric is a lifelong identifier, so the system protects the channel around it instead: trusted capture, liveness, extra factors and resident controls.

  3. Q3/4 Which pattern most strongly suggests cloned fingerprints at a terminal?

    Answer & explanation

    Answer: B

    Fraud with copied prints concentrates many identities on a few devices in odd places. Per-device and per-agency anomaly detection on the authentication log catches it, which is why authentication history is kept.

  4. Q4/4 90 million authentications a day is about how many per second on average?

    Answer & explanation

    Answer: B

    90,000,000 ÷ 86,400 ≈ 1,040 per second, and about 3,000 at a 3× peak: modest throughput, which is why the hard parts are the 200 ms latency and the security, not raw volume.

Wrap-up · 5 min

Interview Conclusion#

Transcript
Interviewer

"Excellent walkthrough. A few quick questions:"

Interviewer

"What's the hardest part of building a system like Aadhaar?"

You

"Three things:

  1. Biometric deduplication at scale — Proving uniqueness against 1.4 billion records requires clever biometric indexing, massive parallel matching, and multi-ABIS cross-checking. Brute force is mathematically impossible.

  2. Inclusive design — The system must work for manual laborers with worn fingerprints, elderly with cataracts, amputees, and people in villages with no electricity. You can't just optimize for the happy path.

  3. Security of the crown jewels — If the CIDR is breached, the biometrics of 1.4 billion people are exposed. There's no 'password reset' for fingerprints. The security architecture must be bulletproof."

Transcript
Interviewer

"What lessons from Aadhaar apply to other large-scale systems?"

You

"Several key lessons:

  1. API-first thinking — Aadhaar was designed as a platform from day one. The authentication API has enabled hundreds of services. DigiLocker, ABDM, e-KYC-based bank and UPI onboarding — all lean on Aadhaar's identity layer.

  2. Vendor neutrality — Three ABIS vendors, open APIs, no lock-in. This enabled continuous improvement and prevented any single vendor from becoming critical.

  3. Offline-first design — For systems that must work in challenging environments, assume no connectivity and design for sync.

  4. Minimal data principle — Aadhaar authentication returns only Yes/No. This minimizes privacy exposure and attack surface.

  5. Invest in inclusion — Adding face authentication made the system accessible to millions who struggled with fingerprints. Inclusion isn't just ethics — it's good engineering."

Summary: Concepts Applied from 10-Week Course#

Concepts from 10-week course in Aadhaar design
Week 1: Data at scale
  • Partitioning: Demographic blocking reduces search space
  • Sharding: CIDR distributed across data centers
  • Replication: Active-active for disaster recovery
Week 2: Failure-first design
  • Offline-first: Enrollment works without connectivity
  • Graceful degradation: Fall back to OTP if biometric fails
  • Timeouts: 200ms latency budget for authentication
  • Retry: Multi-finger, multi-modality attempts
Week 3: Messaging & async
  • Queue-based: Enrollment packets queued for processing
  • Async deduplication: Days/weeks for complex cases
  • Audit trail: All transactions logged asynchronously
Week 4: Caching
  • Template caching: Hot Aadhaars cached for fast auth
  • Session caching: Reduce database lookups
  • Pre-computed: Demographic indices for blocking
Week 5: Consistency
  • Strong consistency: Deduplication must be accurate
  • Consensus: 2-of-3 ABIS agreement for decisions
  • Uniqueness guarantee: Core requirement of the system
Week 9: Security
  • 2048-bit PKI encryption throughout
  • HSM for key management
  • Zero-trust architecture
  • Virtual ID for privacy
  • Tokenization to prevent tracking
Week 10: Operations
  • Multi-vendor: Three ABIS for resilience
  • SLOs: <200ms auth, 99.9% availability
  • Audit: Complete traceability of all operations
  • Continuous innovation: Face auth, VID, e-KYC

Why Aadhaar Matters#

Why Aadhaar is an engineering marvel
Scale
  • Largest biometric database in history (1.4 billion)
  • 170+ billion authentications (March 2026, and counting)
  • 90+ million authentications per day
  • Deduplication: Never done at billion scale before
Inclusion
  • Works in 640,000 villages
  • Handles worn fingerprints, cataracts, amputees
  • Multiple modalities (finger, iris, face, OTP)
  • Low-cost, portable enrollment kits
Impact
  • ₹3.5+ lakh crore in DBT savings
  • Millions of ghost beneficiaries eliminated
  • Identity layer used by UPI onboarding, DigiLocker, ABDM, CoWIN
  • "India Stack" model studied globally
Innovation
  • One of the first multi-ABIS systems at national scale
  • Virtual ID for privacy (before it was trendy)
  • AI-powered face authentication at scale
  • Consent-based data sharing (e-KYC)

"Aadhaar proved that identity infrastructure can be built as a public good, at billion scale, with inclusion at its core."

Self-Assessment Checklist#

After studying this case study, you should be able to:

Architecture:

Architecture0/3 ready
  • Design a biometric enrollment system with offline capability
  • Explain deduplication challenges at billion scale
  • Design authentication with sub-200ms latency

Distributed Systems:

Distributed Systems0/3 ready
  • Apply demographic blocking to reduce search space
  • Design multi-vendor consensus systems
  • Handle offline-first data sync

Security:

Security0/3 ready
  • Implement end-to-end encryption with HSM
  • Design privacy-preserving authentication (VID, tokenization)
  • Apply zero-trust principles to sensitive data

Inclusion:

Inclusion0/3 ready
  • Design for biometric edge cases
  • Provide multiple authentication fallbacks
  • Build systems for challenging infrastructure environments

Sources#

Official UIDAI Documentation:

Statistics and Press Releases:

Architecture and Technical Deep Dives:

Biometric Technology:

Security and Privacy:

Academic and Research:

Further Reading#

Official Documentation:

Engineering Talks:

  • Read

    Dr. Pramod Varma - "Architecting World's Largest Biometric Identity System" (Strata+Hadoop World 2014)

  • Talk

    Nandan Nilekani - Various talks on India Stack and Aadhaar design philosophy

Engineering Blogs:

  • Read

    ByteByteGo: System design breakdowns

  • Read

    High Scalability: Case studies on large-scale systems

  • Read

    Biometric Update: Aadhaar and biometric technology coverage

Books:

  • Book

    "Designing Data-Intensive Applications" by Martin Kleppmann — Distributed systems fundamentals

  • Book

    "Aadhaar: A Biometric History of India's 12-Digit Revolution" by Shankkar Aiyar

  • Book

    "The Aadhaar Effect" by N.S. Ramnath and Charles Assisi

Related Systems to Study:

  • Read

    Estonia e-Residency: European digital identity model

  • Read

    Singapore SingPass: National digital identity

  • Read

    UK Verify: Federated identity approach (contrast to centralized; retired in 2023)

  • Book

    MOSIP: Open-source identity platform (inspired by Aadhaar)

Research Papers:

  • Read

    "India's Aadhaar: Structure, Security, and Vulnerabilities" — IACR ePrint 2022/481

  • Paper

    IEEE/ACM papers on biometric deduplication at scale

"1.4 billion unique identities. 170 billion authentications and counting. The foundation of India's digital public infrastructure."

this lesson in shorts · 4

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Side quest 03 · Aadhaar (UIDAI)
Clear the questFinish Aadhaar.Stages clear as you read them. Marking complete unlocks after ~7 more min of reading.
  • 0/7stages cleared

Follow-up drills

0 of 10 practised

You presented Aadhaar (UIDAI). Now the interviewer pushes back. Answer each follow-up out loud or in the box, then reveal a strong answer.

Follow-up 1 / 10 · scale & numbers

“Why can't you just compare every new enrolment against everyone already enrolled? Show me the numbers.”

Pushes on · whether brute-force deduplication is even possible

Revealing marks it practised.
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

UIDAIDocs · ~90 min

Aadhaar Technology & Architecture: Principles, Design, Best Practices, & Key Lessons

2014
What
UIDAI's own 2014 description of the Aadhaar system, kept on the Internet Archive.
Key idea
Openness, linear scalability, security and vendor neutrality; three ABIS vendors deduplicate every enrolment, on open-source stacks with Hadoop, HBase and MongoDB.
Why here
The primary source for this quest's multi-ABIS deduplication and enrolment pipeline.
taught here: Biometric deduplication

UIDAI no longer hosts this PDF; the Internet Archive copy was opened and its text checked on 2026-10-18.

UIDAIDocs · ~4 min

UIDAI Specifications

What
UIDAI's specification for apps and portals that authenticate residents through the Aadhaar app.
Key idea
An authenticating agency's app or portal calls the Aadhaar app by app-to-app intent or QR code for face authentication and credential exchange, on Android and iOS.
Why here
The current face-authentication path from this quest's deep dive, as UIDAI documents it today.
taught here: Face authentication

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…