writ_registry
The root of trust. Verifies a Groth16 proof of human identity, commits a Poseidon nullifier, and mints a Token-2022 NonTransferable soulbound token to the authority wallet. Called once per human per lifetime.
FrEcFzPx9zqooVp1GmkMdiNXkpgcx3UJRN97YUR9MFTkState
#[account]
#[derive(InitSpace)]
pub struct WritAccount {
pub authority: Pubkey, // 32
pub nullifier: [u8; 32], // 32 — Poseidon(secret, domain)
pub sbt_mint: Pubkey, // 32 — Token-2022 mint, NonTransferable extension
pub created_slot: u64, // 8
pub bump: u8, // 1
}
// Separate nullifier account to prevent account-size blowup.
#[account]
pub struct NullifierRecord {
pub nullifier: [u8; 32],
pub writ: Pubkey,
pub bump: u8,
}PDA seeds
["writ", authority]["nullifier", nullifier[..32]]["sbt", writ]Instructions
register
Consumes a Groth16 proof, verifies it on-chain, stores the nullifier record, mints the SBT.
pub fn register(
ctx: Context<Register>,
proof_a: [u8; 64], // G1 point, uncompressed
proof_b: [u8; 128], // G2 point, uncompressed
proof_c: [u8; 64], // G1 point, uncompressed
public_inputs: [[u8; 32]; 2], // [nullifier, epoch_root]
) -> Result<()>Verification uses Solana's alt_bn128_pairing syscall. The circuit has two public inputs: the committed nullifier and an epoch root. The epoch root binds registration to a specific trusted-setup phase, allowing keys to be rotated over time without invalidating old registrations.
register consumes ~165,000 CU. Clients should prepend a ComputeBudgetProgram.setComputeUnitLimit(220_000) instruction to be safe. The SDK does this automatically.rotate_authority
Transfers ownership of a writ to a new wallet without changing the nullifier. Used for wallet migration, hardware key upgrades, or social recovery. The new authority must sign; the old authority is invalidated atomically.
pub fn rotate_authority(
ctx: Context<RotateAuthority>,
) -> Result<()>All active delegations and reputation state persist across rotation. This is the only way to recover access to a writ whose authority key has been lost.
Accounts (Register)
| Account | Type | Writable | Signer |
|---|---|---|---|
authority | Wallet | yes | yes |
writ_account | PDA | yes | |
nullifier_record | PDA | yes | |
sbt_mint | PDA | yes | |
sbt_token_account | ATA | yes | |
verifier_key | PDA | ||
token_program | Token-2022 Program | ||
system_program | System Program |
Errors
| Code | When |
|---|---|
ProofInvalid | Groth16 pairing check failed |
NullifierCollision | public_inputs[0] already present in NullifierRecord space |
EpochRootMismatch | public_inputs[1] does not match the current verifier_key |
AlreadyRegistered | authority already has a WritAccount (separate from nullifier collision) |
Notes on the SBT
The identity token is minted via Token-2022 with the NonTransferable extension. It cannot be moved, burned by the user, or swapped. Supply is 1, decimals 0. Its only purpose is to provide a standard way for external programs to check registration via existing Token-2022 tooling.
WritAccount and NullifierRecord directly. A program that only integrates with WRIT does not need to touch Token-2022 at all.