Core Concepts

NullifierRecord

The NullifierRecord PDA is how Althea guarantees that one real person can have at most one verified wallet, forever. It is derived deterministically from the user's nullifier — a one-way hash of their idHash.

Account layout

NullifierRecord
#[account]
pub struct NullifierRecord {
    pub nullifier: [u8; 32], // Poseidon(idHash)
    pub created_at: i64,     // unix seconds
    pub bump: u8,
}
impl NullifierRecord {
    pub const SIZE: usize = 32 + 8 + 1;
}

PDA seeds

PDA derivation
seeds = [b"africazk-nullifier", nullifier_bytes]
program_id = AfricaZK1111111111111111111111111111111111

Purpose

Without this account, a user could verify with their real NIN once, wipe their wallet, generate another wallet, and verify again — the same identity attested to multiple wallets. The NullifierRecord PDA is init-only on creation: attempting to derive it a second time fails because the account already exists. One nullifier = one wallet.

Why this doesn't leak identity

The nullifier is Poseidon(idHash) — a one-way hash of an already-hashed ID number. Given a NullifierRecord on-chain, an observer cannot recover the idHash, let alone the NIN. They can only check whether this specific nullifier has been registered.

Linkability across dApps

A dApp that knows a user's nullifier can correlate that user across protocols that also know it. The SDK never exposes the nullifier to your dApp by default — only Althea's Anchor program ever needs it. If you build something that requires nullifier-level deduplication beyond Althea, do so explicitly so users know.

Rent

The account is rent-exempt and paid for by the user on creation. The ~41-byte account costs about 0.0009 SOL.

See also