@p15/compliance
v0.1.0
Published
Protocol15 compliance layer: selective disclosure, verification, one-time burn markers on ZK Compression, and the viewing-key escape hatch
Readme
@p15/compliance
Auditable-when-needed for Solana privacy. The compliance companion to
@p15/stealth: prove what you did
to a named auditor without handing over a god-key.
Two tiers:
- Selective disclosure (
disclosure.ts, preferred): a signed, chain-verifiable statement covering a CHOSEN set of transactions, encrypted to a NAMED auditor's meta-address. Scoped, non-live, revocable, non-repudiable. Optional one-time mode burns an on-chain marker on open. - Viewing key (
audit.ts, escape hatch): epoch-scoped ElGamal + scan secret — a live "open the books" key for ONE epoch. No viewing key risks funds (the spend scalar never leaves the client).
One-time burn markers run on ZK Compression (markers.ts): opening a
one-time disclosure creates a nullifier compressed account at a deterministic
address. A second open reverts at the address tree (addresses are unique), so it
opens at most once and leaves a public record — no server, no custom program,
and a tiny network fee instead of locked rent. Needs a Photon-indexed RPC.
Pre-deposit screening (screening.ts): the ScreeningProvider seam checks
the depositor's address at the one boundary where public funds cross into the
private layer (makePrivate), without adding a wallet popup — screening is an
async pre-check, not a tx. MockScreeningProvider runs offline (CI/devnet/tests);
RangeScreeningProvider is the Range Risk API adapter, inert until RANGE_API_KEY
/ RANGE_API_URL are set. Select with SCREENING_PROVIDER=mock|range.
Install
npm install @p15/compliance @p15/stealthExamples
1. Build + verify a selective disclosure
import { buildDisclosure, verifyDisclosure } from '@p15/compliance';
// Encrypted to the auditor's scan (x25519) key; signed by the holder's spend scalar.
const envelope = buildDisclosure({ records, auditorPublic: auditor.scanPublic, spendScalar: holder.spendScalar });
const verified = verifyDisclosure(auditor.scanSecret, envelope); // throws if tampered / wrong key2. One-time disclosure with an on-chain burn
import { buildDisclosure } from '@p15/compliance';
const envelope = buildDisclosure({ records, holderKeys, auditorMetaAddress, oneTime: true });
// envelope carries an `id`; opening burns the marker (example 3).3. Burn / check the ZK Compression marker
import { openCompressionRpc, isDisclosureOpened, buildBurnDisclosureMarkerIx } from '@p15/compliance';
const rpc = openCompressionRpc(process.env.COMPRESSION_RPC!); // Photon-indexed
if (await isDisclosureOpened(rpc, id)) throw new Error('already opened');
const ix = await buildBurnDisclosureMarkerIx({ rpc, payer: auditorWallet, id });
// sign + send `ix`; a second attempt reverts.4. Pre-deposit screening
import { getScreeningProvider } from '@p15/compliance';
const provider = getScreeningProvider(); // offline mock by default
const { decision } = await provider.screen({ wallet, mint, amount, direction: 'deposit' });
// decision: 'allow' (proceed) | 'review' (soft notice + proceed) | 'deny' (block)Runnable examples: npm run example (disclosure + screening round-trip),
npm run example:disclosure, npm run example:screening, npm run example:marker
(env-gated devnet).
Local development
Not published yet — consumed locally via file: (see
docs/local-dev.md). Depends on @p15/stealth; build
that first.
npm install --legacy-peer-deps
npm test # unit (offline)
npm run test:integration # Range API — RUN_RANGE=1 + creds, else skipped
npm run buildDevnet only. Not for real funds.
Devnet only. Not for real funds.
