Confidential Transactions

Beaconless Confidential Transactions on Bitcoin Cash: RPA Today, PQ Tomorrow, ZK-Ready From Day 1

I’m starting a dedicated BitcoinCashResearch thread for the work I’ve been building in public over the last few months: a path toward beaconless reusable payment addresses and confidential asset transfers on Bitcoin Cash, designed to remain coherent as we move deeper into a post-quantum (PQ) world and into the BCH 2026 VM era.

This post is intentionally written for BCH developers and cryptography experts. It’s the “foundation memo” I want to keep pointing back to as we iterate on implementation details.

Why this direction

I began from the ABLA “SRPA” discussion and quickly learned we needed to be extremely precise about terms. For this post:

  • RPA (Reusable Payment Addresses): the Electron Cash implementation pattern where a static paycode produces fresh, unlinkable child addresses, but requires wallet-side detection + indexing. In Electron Cash specifically, this includes signature grinding and Electrum server support (blockchain.reusable.*) to avoid full-chain scanning.
  • SRPA (Silent Reusable Payment Addresses) (as I’m using it here): achieve the same static-identity → one-time address UX without requiring signature grinding or special server indexing, and with a migration path to covenant-enforced policy + ZK verification.
  • Deterministic restore: the wallet must be recoverable from a mnemonic + a wallet “birthday” (start height) even if the local state file is lost — using bounded scanning and (optionally) on-chain pool/state-cell checkpoints to make restore faster and more reliable.

The core idea I’m building toward is:

Keep the UX of “business-card paycodes”, but move the long-term security and privacy guarantees into enforceable covenants and ZK-verifiable state transitions, while keeping everything compatible with plain Bitcoin Cash transactions.


1) RPA + Post-Quantum: current viability and future key swap

RPA today (secp256k1 era)

Right now, RPA is practical on BCH. It gives us:

  • a static identifier (paycode),
  • unlinkable one-time P2PKH recipients,
  • a workflow that fits inside normal BCH transaction shapes.

But we should be explicit about the security model: classic RPA relies on secp256k1-era assumptions (ECDH for shared-secret derivation, and ECDSA/Schnorr for authorization). In a fully-realized Shor adversary world — i.e., a fault-tolerant quantum computer capable of running Shor’s algorithm at scale — discrete log breaks the elliptic-curve foundation that both ECDH and ECDSA/Schnorr depend on (so privacy/receiver unlinkability and spending security both become fragile).

What “PQ-ready” means here

“PQ-ready” does not mean “already PQ-secure today.” It means:

  1. We treat P2PKH/ECDSA-era flows as transitional.
  2. We progressively migrate value into outputs governed by policy (P2SH/P2S/P2SH32/covenant scripts), so that spending requires satisfying explicit rules.
  3. We design the system so the “paycode → one-time address” interface can swap out its underlying key agreement/signature assumptions without rewriting the whole wallet.

In other words: the UX layer (paycodes, scanning) can remain stable, while the authorization layer (what proves spend authority) transitions from ECDSA-era proofs to PQ signatures and/or ZK proofs enforced by covenant policy.

Why not abandon RPA immediately?

Because ZK doesn’t solve inbound payment UX by itself. In a UTXO chain, someone still needs a receiver surface that works without coordination. RPA (or an SRPA-like successor) is still the best “receiving interface” we have today on BCH while we build the covenant+ZK infrastructure.

So: RPA stays as the front end, while the state cell pool is the policy back end, and ZK becomes the confidentiality engine.


2) Tracking and discovery: solving it with scanning (no beacons)

The current Electron Cash RPA approach works because the wallet scans and indexes. That’s fine, but the goal here is:

  • No protocol-specific beacons
  • No special address prefixes/suffixes that scream “this is stealth”
  • No OP_RETURN markers required for discovery

So we embrace the reality: discovery must be done via scanning, but we make it tractable and deterministic.

Scanning requirements

At minimum, the receiver needs:

  • a scan key and a spend key (or a unified key in early implementations),
  • a bounded scan window (wallet birthdate / start height),
  • a method to identify candidate outputs and test them.

Practical scanning flow (receiver side)

A high-level “RPA output detector” looks like:

  1. Pull transactions (address-based index, mempool, or block range).
  2. For each tx, for each output:
    • if it’s P2PKH, extract the 20-byte hash160.
    • derive candidate one-time keys for plausible (sender, outpoint, index) contexts.
    • compare computed child hash160s to output hash160s.

Complexity may be constrained by:

  • using known input outpoints as anchors,
  • using small indices (e.g. index = 0…k),
  • using bounded scan windows.

The important point: the receiver can discover funds with no sender beacon.


3) Recording scans securely: the local sharded state cell pool

This is the part that’s easiest to misunderstand if you only look at “RPA = stealth addresses”.

What the sharded pool is buying us today

Right now, the pool does three things:

  1. Local state management for a wallet: what have I received, imported, spent, etc.
  2. On-chain enforceable policy for funds that I migrate into the pool: “this UTXO is governed by rules.”
  3. A path to deterministic restore that doesn’t require “scan the entire chain forever”.

This is why I keep describing it as “bringing a ZEC-style global pool local to the wallet.” Zcash’s shielded design is organized around value pools (transparent + shielded pools tracked by consensus), and the mental model of “funds live in a pool and spends update pool state” is proven in production (Zcash value pools reference).

The difference is architectural: I’m not proposing a new global consensus pool on BCH. I’m implementing a local-first pool anchored to wallet keys, with a migration path to broader coordination later if it’s ever needed.

Concretely: our “local pool” is not a consensus object and not a custodial system. It’s a wallet-managed set of ordinary BCH UTXOs that are locked under covenant policies. The only authority is the chain: if a transaction doesn’t satisfy the covenant’s on-chain rules and required signatures, it is invalid and will not be mined. The state file is just an index of on-chain facts — losing it can degrade UX, but it cannot move funds or weaken custody. In that sense, the pool is as trustless as any BCH wallet: the chain enforces validity, and keys enforce authorization.

And we should be explicit about the post-quantum direction: post-quantum vault contracts already exist on BCH Chipnet (Quantumroot), and they demonstrate the practical “policy-UTXO” pivot — move value toward script-locked outputs where spend rules can evolve (Quantumroot vaults on Chipnet). Quantumroot is also explicitly based on conservative stateful hash-based signatures (LM-OTS), which is exactly the class of primitive we expect to remain viable deep into PQ timelines (NIST SP 800-208: Stateful Hash-Based Signatures).

Why sharding matters

Without sharding, every update becomes a single shared state thread — a bottleneck and a coordination hotspot. With sharding:

  • imports map to shards deterministically,
  • updates touch only one shard at a time,
  • the wallet scales operations without global contention.

This is very aligned with the fundamental advantage of UTXO systems: parallelism by default (many independent coins, many independent spends). Quantumroot explicitly calls this out in the context of BCH’s “highly parallel” UTXO architecture (Quantumroot vaults on Chipnet) — sharding is simply leaning into that same property for pool state management.

Deterministic restore goal

If the user loses the device/state file, they should be able to restore by:

  • starting from wallet birth height,
  • scanning only bounded windows,
  • reconstructing pool state from on-chain shard history,
  • re-deriving one-time keys deterministically.

This is still being proven out end-to-end, but the intent is clear: local state is recoverable from chain history + wallet secrets, rather than being a fragile “single JSON file or you’re toast” situation.

Pseudocode: pool state and deterministic shard selection

type PoolState = {
  network: "chipnet" | "mainnet";
  poolIdHex: string;           // 20 bytes hex
  categoryHex: string;         // CashTokens category id
  redeemScriptHex: string;     // covenant redeem script bytecode
  shardCount: number;
  shards: Array<{
    index: number;
    txid: string;
    vout: number;
    valueSats: string;
    commitmentHex: string;     // state commitment (e.g. hash fold)
  }>;
  // records discovered or created by wallet:
  stealthUtxos: Array<StealthUtxoRecord>;
  deposits: Array<DepositRecord>;
  // explicit signer binding (see section 5)
  covenantSigner: {
    pubkeyHash160Hex: string;
  };
};

Deterministic shard index:

function shardSelection(args: {
  depositOutpoint: { txid: string; vout: number };
  receiverHash160Hex: string;
  categoryHex: string;
  shardCount: number;
}): number {
  const preimage = concat(
    hexToBytes(args.categoryHex),
    hexToBytes(args.depositOutpoint.txid),
    uint32le(args.depositOutpoint.vout),
    hexToBytes(args.receiverHash160Hex),
  );
  const h = sha256(preimage);
  return (bytesToUint32(h.slice(0, 4)) % args.shardCount) >>> 0;
}

The exact selector can vary; the point is: no hidden randomness, and the mapping is stable.

4) Application Binary Interface (ABI) shape: useful now (no ZK), ZK-ready later (proofBlob32)

The covenant work I’m doing is not “ZK only.” The covenant is a stable Application Binary Interface (ABI) for spending a specific class of UTXOs — a fixed, consensus-enforced contract for what goes on the unlocking stack, what the script computes, and what outputs must look like.

When I say “ABI” here, I mean:

  • a canonical witness/stack layout (what data is provided when spending),
  • a canonical validation routine (what the covenant checks),
  • a canonical output commitment rule (what the next state must be),
  • and a forward-compatible slot for future proof verification, without changing the transaction shapes later.

ABI concept (high level)

At a high level:

  • Inputs provide:

    • the prior state commitment (read via introspection from the input token commitment),
    • the unlocking witness, e.g. limbs..., noteHash32, and proofBlob32
  • The script:

    • computes nextCommitment deterministically from the witness and current state,
    • asserts that output[0] carries a token commitment equal to the computed nextCommitment,
    • verifies proofBlob32 is exactly 32 bytes

Why proofBlob32 is 32 bytes now

Because 32 bytes is an extremely useful “reserved lane”:

  • it can be a hash pointer to a larger off-chain proof bundle,
  • or a commitment to a transcript (Fiat–Shamir / IOP transcript commitments, etc.),
  • or even a compact proof itself if the chosen scheme allows it.

In other words: the covenant doesn’t need to commit to which ZK system today, but it can still reserve a consensus-validated interface slot for proofs, so we don’t paint ourselves into an ABI corner later.


5) Phase 2: stealth identity transactions with RPA + sharded pool

Phase 2’s privacy target is identity stealth, not full amount hiding.

That means:

  • external observers should not be able to trivially link sender identity to receiver identity,
  • paycode recipients look like normal P2PKH outputs,
  • wallet discovery is beaconless (scan-based),
  • wallet can import discovered UTXOs into covenant-controlled shards.

Phase 2 flow (high-level)

Pool init (Bob):

  • Bob initializes a local pool of N shards.
  • Stores poolState locally (and can reconstruct later from chain history).

Alice → Bob (deposit):

  • Alice pays to Bob’s paycode-derived one-time P2PKH.
  • Alice records a deposit record (optional but helpful).

Bob observes + imports:

  • Bob scans, detects the one-time output.
  • Derives the spend key (one-time priv).
  • Imports it into a chosen shard via a covenant spend that updates shard commitment.

Bob withdraws:

  • Bob spends from the shard to pay to Alice’s paycode-derived one-time address.
  • Fees are handled with a transparent input (for now).

Locking the pool to its initialization key (attack surface reduction)

A priority requirement is:

the pool must only be mutable by the key(s) it was initialized with.

In practice, Phase 2 does this by:

  • binding covenant authorization to covenantSigner.pubkeyHash160Hex,
  • requiring spends to produce a signature under that key (or a policy threshold),
  • enforcing output commitments match computed transitions.

This is the “policy UTXO” pivot that matters for the PQ roadmap: we’re moving value toward outputs whose spending rules can evolve.


6) Phase 3: full stealth identity + hidden amounts (ZK + nullifiers)

Phase 3 is where we move from placeholder commitments and “trust the off-chain logic” assumptions to cryptographic enforcement: spends carry proofs that the transition is valid, double spends are prevented via nullifiers, and value conservation (including range constraints) is proven rather than inferred.

What changes in Phase 3

  • The pool becomes a true privacy pool (even if local-first).
  • Spending requires:
    • a proof of membership / authorization,
    • a nullifier to prevent double spends,
    • and a proof about amounts (range proof or balance conservation).

The covenant ABI already accepts proofBlob32, so the transaction shapes don’t need to be reinvented.

“Nullifier-ish” commitments

Right now we have placeholder commitment updates (hash folds, etc.). In Phase 3, the shard commitment becomes a structure that supports:

  • membership (e.g., Merkle-style accumulator or incremental hash fold),
  • a nullifier set (or compact nullifier commitment),
  • balance conservation rules.

The exact cryptographic construction is not finalized in this post, but the state transition plug is.


Summary: what the state cell sharded pool is really for

It’s not “just tracking stealth change.”

It’s the bridge between:

  • RPA as a practical UX layer today, and
  • covenant policy + ZK proofs as the enforcement layer tomorrow.

You can view it as:

  • a local-first privacy pool,
  • an on-chain policy vault,
  • a deterministic restore anchor,
  • a stable ABI interface for future proofs.

Next steps

I’m using this thread as the foundation for:

  1. Finalizing the public CLI interfaces and removing demo hardcoding
  2. Implementing beaconless scan+record for RPA outputs
  3. Tightening pool authorization (init key ownership enforcement)
  4. Defining Phase 3 proof semantics for proofBlob32 and nullifiers

As always: the goal is to keep everything compatible with plain BCH transaction flows while moving aggressively toward PQ-ready, covenant-enforced, ZK-verifiable confidentiality.

If you have feedback on:

  • the shard selection function,
  • the restore model,
  • the proof ABI design,
  • or the PQ migration story,

please jump in — this is exactly the kind of iteration that needs expert review early.

1 Like