# Selective disclosure

> Per-transaction disclosure without revealing wallet history: the cryptographic foundation under payment receipts.

Privacy Pools v2 supports per-transaction selective disclosure as a first-class primitive. The cryptographic foundation is the `noteSecret` generated for every output note: knowing that one secret lets you recompute exactly one note's commitment, and nothing else.

## The setup

When a sender calls `prepareTransfer`, the SDK generates a fresh `noteSecret` for each output note and returns it on `executeOptions.recipientPendingNotes[i].noteSecret`. This secret:

-   Is required to recompute the on-chain commitment (via the Poseidon chain documented in [Commitments & nullifiers](commitments-and-nullifiers)).
-   Cannot be recovered from the on-chain state alone. It's known only to the sender at prepare time and (in [discoverable mode](discoverable-vs-oob)) to the recipient via their [viewing key](keys).
-   Is independent across transactions: disclosing one secret reveals exactly one note, never affects any other.

## How a third party verifies a single payment

Given a receipt JSON (the same shape [generate a receipt](../operations/generate-receipt) produces: note fields under `receipt.note.*`, the pool under `receipt.pool.*`), anyone with public RPC access can verify it client-side:

```ts

// Minimal read-only ABI fragment; POOL_VAULT_ABI is not value-importable
// from the package entrypoint.
const POOL_VAULT_ABI = [
    {
        type: "function",
        name: "commitments",
        inputs: [{ name: "_commitmentHash", type: "uint256" }],
        outputs: [{ name: "createdAt_", type: "uint256" }],
        stateMutability: "view",
    },
] as const;

const hash = await PoseidonHashService.create();
const note = receipt.note;

// 1. Recompute noteAddressHash
const noteAddressHash = hash.hash([note.ownerAddress, note.noteSecret]);
assert(BigInt(noteAddressHash) === BigInt(note.noteAddressHash));

// 2. Recompute commitment
const precommitment = hash.hash([noteAddressHash, note.tokenId, note.value, "0x0"]);
const commitment    = hash.hash([precommitment, note.label]);
assert(BigInt(commitment) === BigInt(note.commitment));

// 3. Confirm the commitment is on-chain
const timestamp = await publicClient.readContract({
    address: receipt.pool.poolAddress,
    abi: POOL_VAULT_ABI,
    functionName: "commitments",
    args: [BigInt(commitment)],
});
assert(timestamp !== 0n);
```

If all three checks pass, the receipt is proof that: *this exact value of this asset was inserted into the pool at this commitment, addressed to this recipient.* The third party sees nothing else about the sender's wallet history.

## What a third party does not learn

-   Other payments the sender made.
-   The sender's other notes or balances.
-   The sender's nullifying / revocable / viewing keys.
-   Other recipients in the same cycle / batch.

## Mental model

Think of the noteSecret as a one-time viewing key scoped to a single payment. The user doesn't need to set anything up ahead of time, because every transfer the SDK builds already produces this secret as a byproduct.

For implementation, see [Generate a payment receipt](../operations/generate-receipt) (usage) and [receipt guide](../recipes/receipt-export-and-audit) (end-to-end flow).
