# Security

## What is enforced on chain

- Bundle sizes are only 1, 2, 4, 8. Other sizes are rejected.
- One bundle per wallet, enforced by the MintRecord.
- Fragment cap 88,888 and badge cap 11,111.
- Merge burns exactly 8 fragments and mints exactly 1 Badge.
- MintBundle requires the verifier signer. A direct program call
  without that signature fails.
- Paid mint payments must use the configured USDC mint. Any other
  mint is rejected.
- Marketplace settlement splits the fee as specified, and only
  through the program marketplace.
- Payment destination owners are checked on chain. In Buy, the
  seller payment account must be owned by the listing seller,
  the vault payment account by the configured vault, and the
  Dev payment account by the configured Dev, and the buyer
  token account must be owned by the buyer. In MintBundle,
  the vault and Dev payment accounts must be owned by the
  configured vault and Dev, including for the free bundle.
  These checks run before any token transfer.
- The config account is checked on chain. A shared helper
  verifies that the config account equals the PDA derived
  from the seed "config" and is owned by the program, in
  List, MintBundle, Merge, Buy, CancelList, and
  UpdateConfig. Initialize keeps its own PDA check because
  it creates the account.

## Program findings, fixed

Three findings from an attack review of the program are now
fixed in program/src/processor.rs, with regression tests.
Cargo test passes 23 tests, including 7 new regression tests
for these fixes. The fixes are:

- P-CRITICAL-1, Buy payment destinations: fixed as described
  above. Before the fix, a buyer calling the program
  directly could pass their own accounts for all three
  payment destinations and acquire a listing for a net
  payment of zero. That path no longer works.
- P-CRITICAL-2, List config check: fixed as described
  above. Before the fix, List read config values without
  verifying the config account was the real config PDA,
  so a fake Config shaped account could be used to create
  listings that later drained the real pooled escrow on
  Buy. Fake listings can no longer be created this way.
- P-HIGH-1, MintBundle payment owners: fixed as described
  above. Before the fix, vault and Dev payment account
  owners were unchecked, so under verifier key compromise
  or service misconfiguration, mint payments could divert
  to attacker accounts. That diversion path is now checked
  on chain.

## Service fix: minted lock and signed bind

- Bind requires a Solana message signature. The caller
  signs a message containing the handle, the Solana
  address, and a server issued nonce, and the service
  verifies the signature before writing any binding.
  Message signing is not a transaction and costs no gas.
  An EVM signature is verified when supplied, and is
  optional: a bind without one proves Solana wallet
  control only, not BSC handle ownership.
- Bind is first-bind-wins per handle through the API. A
  different wallet gets refused before mint and after
  mint, and only the operator reset procedure can change
  a binding: the requester proves BSC control out of
  band, the operator checks on chain that no MintRecord
  exists for the previously bound wallet, and only then
  deletes the binding entry.
- Prepare-mint sets the binding to minted immediately
  after signing, while the transaction is still
  unconfirmed, and never resets that flag on rebind. It
  also checks the on-chain MintRecord before signing and
  refuses if one already exists. If a prepared mint
  transaction never lands, the operator reset procedure
  above applies after the MintRecord check.

## What is not enforced

- Fee on peer to peer transfers or on other marketplaces. Those
  trades do not call this program.
- Sybil resistance beyond the handle binding. A person with
  multiple Binance Agent IDs could hold multiple handles. The
  binding raises the cost from free wallet creation to a new
  Agent ID per extra bundle. It does not make multiple accounts
  impossible.
- Verifier key safety. The verifier key is a hot key held by the
  operator. If it leaks, an attacker can mint without .ai proof
  until the program authority rotates it. Rotation is a program
  authority transaction, documented in the deploy guide.
  Detection is reconciling on-chain MintRecords against
  prepare-mint logs and bindings, alerting on any MintRecord
  with no matching service record.

## Residual risks, stated plainly

None of the items below is fixed. They are recorded here so
no reader mistakes them for fixes, and so no later round
drops them silently.

- Buy listing PDA non-derivation. Buy takes no listing_id
  argument, so the program cannot re-derive the listing PDA
  from seeds. Mitigations in place: Buy checks the listing
  account is program owned, fake listing creation is closed
  in List, and the service prepare-buy derives and fetches
  the exact PDA from seller plus listing_id. Closing this
  fully on chain would need a program change to carry
  listing_id in Buy, and that is not ordered.
- First-binder squatting without EVM proof. A Solana
  signature proves Solana wallet control only. Without an
  EVM signature, anyone can bind any verified handle to
  their own wallet, because BSC verification is a public
  read. Mitigations: first-bind-wins, the minted lock, the
  operator reset procedure, and the optional verified EVM
  signature. Full closure requires requiring the EVM
  signature at bind time; the front end can already
  produce one through its optional EVM signing step, so
  the remaining change is the service-side policy change.
- Handle level single bundle, service only. The program
  MintRecord is per wallet, and the handle hash is stored
  but never uniqueness checked on chain. The service
  minted lock is the only handle level control. A handle
  can mint for many fresh wallets if the service lock is
  bypassed, for example by a direct program call with a
  compromised verifier key.
- Prepare-list listing_id race. Two concurrent prepare-list
  calls for the same seller can return the same id. The
  program rejects the second transaction (listing account
  already initialized), and the client retries for a fresh
  id.
- Prepare-buy numeric id recovery. The listings fetch
  returns the listing account address, not the numeric
  listing_id seed, and the numeric id is not stored on
  chain. The front end recovers it locally only when the
  derived PDA for an id in 0 to 999 matches the listing
  account exactly; otherwise the buyer must get the id
  from the seller out of band. The prepared buy still
  checks expected price and amount against the on-chain
  listing at prepare time, so a wrong id or stale price
  aborts with no transaction built.
- Initialize front-running. The first Initialize call owns
  the config, so the deployer must Initialize immediately
  after program deploy, before publishing the program ID
  anywhere, and verify config on chain.
- Authority centralization. UpdateConfig lets the
  authority instantly change vault, Dev, and verifier
  with no timelock. The authority key must be held by the
  user only, with rotation and config change monitoring.
- Merge prepare-to-sign state change. Prepare-merge
  pre-checks are point in time. If fragments are spent or
  the Badge cap fills between prepare and sign, the
  on-chain Merge fails and the user pays only network gas;
  no fragments burn and no Badge mints on a failed
  transaction. The front-end preview warns of exactly this.
- Badge ATA creation race. If a Badge token account is
  created between prepare and sign, the prepared ATA
  creation instruction targets an existing account and the
  transaction fails for gas only. The user re-prepares,
  now with no creation instruction, and signs again.
- Token-2022 layout constraint. Service token account
  parsing assumes the SPL Token layout, which is the
  layout the program itself reads. Token-2022 accounts
  with extensions are not parsed here, so the mints
  deployed for this program must be SPL Token mints.
- On-chain settlement unproven. Program CPI paths and
  market settlement on a validator are not proven by cargo
  tests or offline service tests. That proof is the devnet
  test in a real browser with a real wallet, which has not
  run yet.
- File-store bindings. Bindings are stored in a file by
  default in this build. For production use, replace the
  file store with a database before launch. The file store
  is a v1 simplicity choice, documented in the service
  README, not a scaling claim.

## Before you pay, check

- The deployed program ID shown in the app matches the deploy
  record.
- The vault address and Dev address shown in the app match the
  deploy record.
- The bundle you selected is 1, 2, 4, or 8, and the total matches
  Mint: free, $10, $25, or $50.
- The transaction your wallet shows has the program ID above and
  no instruction you did not expect. If it does not match, do not
  sign it.
- No one in this project will ever ask for your private key or
  seed phrase. A request for either is a scam.
