Skip to content
Prividium

Selective Disclosure

Selective disclosure is a privacy feature that allows contract owners to make specific pieces of information publicly accessible while maintaining the confidentiality of all other contract data.

The following contract information can be disclosed:

  • Account data (bytecode, balance, nonce and account metadata).
  • Token total supply.
  • Token holder balance.

Selective disclosure is disabled by default. To enable it, set DISCLOSURE_METHODS_ENABLED=true on the Permissions API and VITE_DISCLOSURE_METHODS_ENABLED=true on the Admin Panel.

Step-by-Step Process

1. Enable Disclosure

Disclosure settings are managed directly on the contract configuration form:

  1. Go to the Contracts page in the Admin Dashboard.
  2. Click Create Contract to register a new one, OR click on an existing contract to edit it.
  3. In the contract form, expand the Data disclosure section:
    • Check Disclose account data to expose the address's full account-properties record (bytecode, balance, nonce and account metadata).
    • Check Disclose Erc 20 Supply to expose total supply (requires the contract to be an ERC-20).
    • Optionally set Disclosure start block to restrict disclosure queries to blocks at or after a specific block number. Requests for earlier blocks are rejected with an error. Defaults to 0 (no minimum).
  4. Click Save (or Create).

2. Configure Disclosed Addresses (ERC-20 only)

If ERC-20 Supply Disclosure is enabled, you can specify addresses whose token balances may be queried publicly via prividium_tokenBalanceDisclosure.

  1. Navigate to the contract details page.
  2. Look for the Disclosed Addresses section.
  3. Add addresses to allow public balance queries for each.
  4. Save your changes.

3. Request Data

Once configured, users can query the data using specific JSON-RPC methods without needing an authentication token.

Available Disclosure Types

1. Account Data Disclosure

Reveals a single address's account-properties record, which includes bytecode, balance and nonce, along with the full cryptographic proof material needed to verify that record against the L1 state commitment. Works for both contracts and EOAs.

Purpose:

  • Verify a contract's deployed bytecode matches expected values.
  • Provide verifiable transparency for an address's balance and nonce without exposing unrelated state.
  • Enable auditors and third parties to confirm an account's on-chain state against L1 without private RPC access.

RPC Endpoint: prividium_accountDataDisclosure

Request:

{
  "method": "prividium_accountDataDisclosure",
  "params": ["0x1234567890123456789012345678901234567890", "0x1a2b3c"]
}

Parameters:

  • params[0] (required): Address to query (contract or EOA).
  • params[1] (required): Hex-encoded block number. Must be at or after the contract's disclosure start block.

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "accountProperties": {
      "versioningData": "0x0101010000000000",
      "nonce": "0x01",
      "balance": "0x0de0b6b3a7640000",
      "bytecodeHash": "0xabcd...",
      "unpaddedCodeLen": 1024,
      "artifactsLen": 16,
      "observableBytecodeHash": "0x1234...",
      "observableBytecodeLen": 1024
    },
    "address": "0x1234567890123456789012345678901234567890",
    "bytecode": "0x6080604052...",
    "batchNumber": 175,
    "stateCommitmentPreimage": {
      "nextFreeSlot": "0x430",
      "blockNumber": "0x1da",
      "last256BlockHashesBlake": "0x7e2e68a1633628cf4ee6416a444a6af248e7cf975b3b744b6068beb14f8e98e4",
      "lastBlockTimestamp": "0x69dda64c"
    },
    "l1VerificationData": {
      "batchNumber": 175,
      "numberOfLayer1Txs": 0,
      "priorityOperationsHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
      "dependencyRootsRollingHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "l2ToL1LogsRootHash": "0x692f35c99f9c698852289ffecf07f6dd45770904521149d79aa85aae598fa375",
      "commitment": "0x9dbc02b99ea4c385ef2835c23affe9cf84004b946b107b2351eee54f2c641f9b"
    },
    "storageProof": {
      "type": "existing",
      "index": 742,
      "value": "0x...",
      "nextIndex": 903,
      "siblings": ["0xa1b2c3d4...", "0xe5f6a7b8...", "..."]
    }
  }
}

Implementation Details:

  • ZKsync OS stores the hash of account data in a virtual system contract with address 0x0000000000000000000000000000000000008003.
  • The method returns all the data needed to reconstruct this hash.
  • Storage proofs are also sent, to be able to reconstruct proof this data using data posted in l1.

Verifying account data using Prividium SDK:

disclosure-account-data.ts
import { selectiveDisclosureActions, verifyAccountPropertiesProof } from 'prividium';
import type { Address } from 'viem';
import { createPublicClient, http } from 'viem';

const PRIVIDIUM_RPC_URL = process.env.PRIVIDIUM_RPC_URL || 'http://localhost:8000/rpc';
const L1_RPC_URL = process.env.L1_RPC_URL || 'http://localhost:5010';
const L1_DIAMOND_PROXY_ADDRESS = (process.env.L1_DIAMOND_PROXY_ADDRESS ||
    '0x18f438bc08d755e164a7ae7c077e2ea93b0179ef') as Address;

export async function verifyAccountData(address: Address, blockNumber: bigint) {
    const prividium = createPublicClient({ transport: http(PRIVIDIUM_RPC_URL) }).extend(selectiveDisclosureActions);
    const l1Client = createPublicClient({ transport: http(L1_RPC_URL) });

    const disclosure = await prividium.accountDataDisclosure(address, blockNumber);
    const expectedBytecode = disclosure.bytecode === '0x' ? undefined : disclosure.bytecode;
    const ok = await verifyAccountPropertiesProof(
        disclosure,
        l1Client,
        L1_DIAMOND_PROXY_ADDRESS,
        expectedBytecode,
        address
    );
    return ok;
}

2. Token Supply Disclosure

Reveals an ERC-20 token's total supply along with cryptographic storage proofs, enabling independent verification against the ZKsync state commitment.

Purpose:

  • Provide verifiable transparency for token total supply without exposing individual holder balances.
  • Enable auditors, DEXs, and aggregators to confirm supply figures on-chain.

What's Disclosed:

  • result: Hex-encoded total supply value returned by totalSupply()
  • callData: The ABI-encoded call used to retrieve the value
  • batchNumber: The L1 batch number at which the proof was generated
  • stateCommitmentPreimage: ZKsync state commitment data for verification
  • l1VerificationData: Data required for L1 proof verification
  • proofs: Storage proofs for each contract slot read during execution

RPC Endpoint: prividium_tokenSupplyDisclosure

Request:

{
  "method": "prividium_tokenSupplyDisclosure",
  "params": ["0x1234567890123456789012345678901234567890", "0x1a2b3c"]
}

Parameters:

  • params[0] (required): ERC-20 token contract address
  • params[1] (required): Hex-encoded block number. Must be at or after the contract's disclosure start block.

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "result": "0x00000000000000000000000000000000000000000000000000000000000006e4",
    "callData": "0x18160ddd",
    "batchNumber": 175,
    "stateCommitmentPreimage": {
      "nextFreeSlot": "0x430",
      "blockNumber": "0x1da",
      "last256BlockHashesBlake": "0x7e2e68a1633628cf4ee6416a444a6af248e7cf975b3b744b6068beb14f8e98e4",
      "lastBlockTimestamp": "0x69dda64c"
    },
    "l1VerificationData": {
      "batchNumber": 175,
      "numberOfLayer1Txs": 0,
      "priorityOperationsHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
      "dependencyRootsRollingHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "l2ToL1LogsRootHash": "0x692f35c99f9c698852289ffecf07f6dd45770904521149d79aa85aae598fa375",
      "commitment": "0x9dbc02b99ea4c385ef2835c23affe9cf84004b946b107b2351eee54f2c641f9b"
    },
    "proofs": [
      {
        "address": "0x7859dcea64a8f60d9ec8f31521574618d8ae8fe1",
        "storageProofs": [
          {
            "key": "0x0000000000000000000000000000000000000000000000000000000000000003",
            "proof": {
              "type": "existing",
              "index": 691,
              "value": "0x00000000000000000000000000000000000000000000000000000000000006e4",
              "nextIndex": 1056,
              "siblings": ["0xf213959a...", "0x355510938b...", "..."]
            }
          }
        ]
      }
    ]
  }
}

Implementation Details:

  • Data Retrieval: Debug-traces a totalSupply() call, collects touched storage slots, and gathers zks_getProof for each slot.
  • Authorization: Permissions API's checkErcTokenSupply verifies supply disclosure is enabled for the contract.

Verifying a token supply disclosure using Prividium SDK:

disclosure-token-supply.ts
import { selectiveDisclosureActions, verifyEthCallDisclosure } from 'prividium';
import { type Address, createPublicClient, http } from 'viem';

const PRIVIDIUM_RPC_URL = process.env.PRIVIDIUM_RPC_URL || 'http://localhost:8000/rpc';
const L1_RPC_URL = process.env.L1_RPC_URL || 'http://localhost:5010';
const L2_RPC_URL = process.env.L2_RPC_URL || 'http://localhost:5050';
const L1_DIAMOND_PROXY_ADDRESS = (process.env.L1_DIAMOND_PROXY_ADDRESS ||
    '0x18f438bc08d755e164a7ae7c077e2ea93b0179ef') as Address;

export async function verifyTokenSupplyDisclosure(tokenAddress: Address, blockNumber: bigint) {
    const prividium = createPublicClient({ transport: http(PRIVIDIUM_RPC_URL) }).extend(selectiveDisclosureActions);
    const l1Client = createPublicClient({ transport: http(L1_RPC_URL) });
    const l2Client = createPublicClient({ transport: http(L2_RPC_URL) });

    const disclosure = await prividium.tokenSupplyDisclosure(tokenAddress, blockNumber);
    const bytecodes = { [tokenAddress.toLowerCase()]: (await l2Client.getCode({ address: tokenAddress }))! };
    const ok = await verifyEthCallDisclosure({
        disclosure,
        l1Client,
        l2Client,
        diamondAddress: L1_DIAMOND_PROXY_ADDRESS,
        contractBytecodes: bytecodes,
        batchNumber: disclosure.batchNumber
    });
    return ok;
}

3. Token Balance Disclosure

Reveals the token balance of a specific holder address along with cryptographic storage proofs.

Purpose:

  • Allow public verification of balances for pre-approved addresses (e.g., disclosed treasury or vesting wallets).
  • Enable third parties to confirm a specific holder's balance without accessing private RPC.

What's Disclosed:

  • result: Hex-encoded balance value returned by balanceOf(holderAddress)
  • callData: The ABI-encoded call used to retrieve the value
  • batchNumber: The L1 batch number at which the proof was generated
  • stateCommitmentPreimage: ZKsync state commitment data for verification
  • l1VerificationData: Data required for L1 proof verification
  • proofs: Storage proofs for each contract slot read during execution

RPC Endpoint: prividium_tokenBalanceDisclosure

Request:

{
  "method": "prividium_tokenBalanceDisclosure",
  "params": ["0x1234567890123456789012345678901234567890", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x1a2b3c"]
}

Parameters:

  • params[0] (required): ERC-20 token contract address
  • params[1] (required): Holder address to query
  • params[2] (required): Hex-encoded block number. Must be at or after the contract's disclosure start block.

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "result": "0x0000000000000000000000000000000000000000000000000000000000000064",
    "callData": "0x70a0823100000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8",
    "batchNumber": 175,
    "stateCommitmentPreimage": {
      "nextFreeSlot": "0x430",
      "blockNumber": "0x1da",
      "last256BlockHashesBlake": "0x7e2e68a1633628cf4ee6416a444a6af248e7cf975b3b744b6068beb14f8e98e4",
      "lastBlockTimestamp": "0x69dda64c"
    },
    "l1VerificationData": {
      "batchNumber": 175,
      "numberOfLayer1Txs": 0,
      "priorityOperationsHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
      "dependencyRootsRollingHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "l2ToL1LogsRootHash": "0x692f35c99f9c698852289ffecf07f6dd45770904521149d79aa85aae598fa375",
      "commitment": "0x9dbc02b99ea4c385ef2835c23affe9cf84004b946b107b2351eee54f2c641f9b"
    },
    "proofs": [
      {
        "address": "0x7859dcea64a8f60d9ec8f31521574618d8ae8fe1",
        "storageProofs": [
          {
            "key": "0x000000000000000000000000000000000000000000000000000000000000000a",
            "proof": {
              "type": "existing",
              "index": 742,
              "value": "0x0000000000000000000000000000000000000000000000000000000000000064",
              "nextIndex": 903,
              "siblings": ["0xa1b2c3d4...", "0xe5f6a7b8...", "..."]
            }
          }
        ]
      }
    ]
  }
}

Implementation Details:

  • Data Retrieval: Debug-traces a balanceOf(holderAddress) call, collects touched storage slots, and gathers zks_getProof for each slot.
  • Authorization: Permissions API's checkBalanceDisclosure verifies that the holder address is in the contract's disclosed addresses list.

Verifying a token balance disclosure using Prividium SDK:

disclosure-token-balance.ts
import { selectiveDisclosureActions, verifyEthCallDisclosure } from 'prividium';
import { type Address, createPublicClient, http } from 'viem';

const PRIVIDIUM_RPC_URL = process.env.PRIVIDIUM_RPC_URL || 'http://localhost:8000/rpc';
const L1_RPC_URL = process.env.L1_RPC_URL || 'http://localhost:5010';
const L2_RPC_URL = process.env.L2_RPC_URL || 'http://localhost:5050';
const L1_DIAMOND_PROXY_ADDRESS = (process.env.L1_DIAMOND_PROXY_ADDRESS ||
    '0x18f438bc08d755e164a7ae7c077e2ea93b0179ef') as Address;

export async function verifyTokenBalanceDisclosure(tokenAddress: Address, holderAddress: Address, blockNumber: bigint) {
    const prividium = createPublicClient({ transport: http(PRIVIDIUM_RPC_URL) }).extend(selectiveDisclosureActions);
    const l1Client = createPublicClient({ transport: http(L1_RPC_URL) });
    const l2Client = createPublicClient({ transport: http(L2_RPC_URL) });

    const disclosure = await prividium.tokenBalanceDisclosure(tokenAddress, holderAddress, blockNumber);
    const bytecodes = { [tokenAddress.toLowerCase()]: (await l2Client.getCode({ address: tokenAddress }))! };
    const ok = await verifyEthCallDisclosure({
        disclosure,
        l1Client,
        l2Client,
        diamondAddress: L1_DIAMOND_PROXY_ADDRESS,
        contractBytecodes: bytecodes,
        batchNumber: disclosure.batchNumber
    });
    return ok;
}