Core Concepts
The Anchor Program
The Althea program lives on Solana, written with the Anchor framework. It exposes one instruction (submit_proof) and manages two PDAs (NullifierRecord and AttestationRecord). It enforces everything the off-chain pipeline can't.
submit_proof instruction
programs/africazk/src/lib.rs
use anchor_lang::prelude::*;
declare_id!("AfricaZK1111111111111111111111111111111111");
#[program]
pub mod africazk {
use super::*;
pub fn submit_proof(
ctx: Context<SubmitProof>,
proof_bytes: Vec<u8>,
public_signals: [u8; 64], // valid (32) + nullifier (32)
ax: [u8; 32],
ay: [u8; 32],
) -> Result<()> {
require!(public_signals[..32] == ONE_LE, ErrorCode::InvalidProof);
require!(ax == AFRICAZK_AX, ErrorCode::WrongIssuer);
require!(ay == AFRICAZK_AY, ErrorCode::WrongIssuer);
// light-protocol verifier hook lives here in a future release
verify_groth16(&proof_bytes, &public_signals, &ax, &ay)?;
let attestation = &mut ctx.accounts.attestation;
attestation.verified = true;
attestation.timestamp = Clock::get()?.unix_timestamp;
attestation.protocol = *b"Althea-v1\0\0\0\0\0";
attestation.revoked = false;
Ok(())
}
}Accounts
programs/africazk/src/accounts.rs
#[derive(Accounts)]
#[instruction(proof_bytes: Vec<u8>, public_signals: [u8; 64])]
pub struct SubmitProof<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
payer = user,
space = 8 + NullifierRecord::SIZE,
seeds = [b"africazk-nullifier", &public_signals[32..]],
bump
)]
pub nullifier_record: Account<'info, NullifierRecord>,
#[account(
init,
payer = user,
space = 8 + AttestationRecord::SIZE,
seeds = [b"africazk-attestation", user.key().as_ref()],
bump
)]
pub attestation: Account<'info, AttestationRecord>,
pub system_program: Program<'info, System>,
}Both PDAs are init accounts — they fail if they already exist. That fail-on-init is the entire duplicate-prevention story for nullifiers, and the attestation-uniqueness story per wallet.
Error codes
| Code | Cause |
|---|---|
| InvalidProof | valid signal is not 1, or Groth16 verification fails |
| WrongIssuer | Ax/Ay do not match the program-baked Althea public key |
| DuplicateNullifier | NullifierRecord PDA already exists — this identity has been used |
| DuplicateAttestation | AttestationRecord PDA already exists — this wallet already verified |
Upgrade path
Current builds inline a stub Groth16 verifier. The architecture is designed to slot in Light Protocol's on-chain Groth16 verifier as a drop-in replacement of verify_groth16 — the instruction signature and PDA layout stay identical, so existing dApps and SDK callers don't need to change.