UPP — Universal Private PoolConcepts

ASP Compliance

Association Set Provider architecture — how UPP enforces compliance without revealing user identity.

ASP Compliance

What is an ASP?

An Association Set Provider maintains a Merkle tree of approved addresses. When withdrawing from the pool, users prove their funds' origin is in an ASP's allowlist — without revealing which origin address they are.

How It Works

  1. Monitor deposits: ASP watches Shield events, checks if the depositor meets criteria
  2. Build Merkle tree: Approved addresses are organized in a Poseidon-hashed LeanIMT
  3. Publish root: Updated Merkle root is posted on-chain (per-block or on-schedule)
  4. User withdrawal: The ZK circuit proves that the note's origin is in the ASP's tree
// ASP backend (using @permissionless-technologies/upc-sdk)
pool.on('Shield', async (commitment, origin) => {
  if (await meetsCriteria(origin)) {
    await asp.addMember(origin)
    await asp.publishRoot({ walletClient })
  }
})

Origin Semantics

Origin is the address that originally deposited funds into the pool.

Alice shields → origin = Alice
Alice → Bob transfer → origin still = Alice
Bob merges Alice's note → origin = Bob (Bob takes compliance responsibility)

Origin is:

  • Preserved through all transfers
  • Only changed by merge (merger becomes new origin)
  • The address checked by ASPs at withdrawal

Withdrawal Paths

Ragequit

Original depositors can always withdraw their own funds, regardless of ASP status. No hostage situations.

If an address is later sanctioned and removed from all ASPs, they can still execute a ragequit — withdrawing only to their own address (origin == recipient). Notes they previously sent to others become restricted, requiring the recipient to merge (taking on origin) or the original depositor to ragequit.

ASP Check Timing

ASP checks happen at exit, not entry. This is intentional:

OperationASP CheckNotes
ShieldNoneAnyone can deposit
TransferRequiredOrigin must be in an ASP
MergeNoneOrigin changes to merger
Withdraw (normal)RequiredOrigin must be in ASP tree
Withdraw (ragequit)NoneRecipient must equal origin

Multiple ASPs

Different ASPs serve different use cases:

ASP TypeCriteriaUse Case
OFAC sanctions screenAddress not on OFAC SDN listGeneral compliance
Institutional KYCVerified institutional entityB2B transactions
Exchange KYCPassed exchange KYCExchange-integrated users
Government sanctionsNational sanctions listJurisdictional compliance

Recipients can require specific ASPs:

// A protocol that only accepts withdrawals from its own KYC ASP
function receiveFromPool(bytes calldata proof) external {
    require(verifyASPMembership(proof, EXCHANGE_ASP_ID));
}

Privacy Properties

ASPs know:

  • The list of all depositors (Shield events are public)
  • Which addresses meet their criteria

ASPs do NOT know:

  • Who is withdrawing (the ZK proof hides identity)
  • Which specific origin is being used in any given withdrawal
  • Transaction patterns inside the pool

ASPs cannot censor specific withdrawals — they can only maintain their membership list.

The ASP compliance layer is built on @permissionless-technologies/upc-sdk. See the UPC documentation for running your own ASP.

On this page