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.

5 Likes

TL:DR I’ve been working to develop an on ramp to confidential / stealth transactions and have created a flow compatible with cash fusion. So if users spend only fused coins, they can theoretically pay to a paycode where identity is detached from outputs and changes to self paycode. Then, recipient imports to state pool where full confidentiality may take place. It’s basically:

  1. Transparent Chain = Public Blockchain
  2. State Pool = Stealth

Where users may import from transparent chain to stealth pool and vice versa.


I’m excited about the following console outputs, that probably look abstract to most, but it’s proving that months of work is all worth it.

The following console logs demonstrate a cli control plane utility I’ve been working to facilitate the testing of confidential tx that has morphed into a utility product that I’m excited to release as a bonus. For anyone familiar with Kubernetes, it’s quite similar to kubectl, just for BCH. So I’m calling it bchctl for now and the idea is to leverage templates to perform blockchain operations in a standard fashion.

For me, it’s been super handy to quickly and easily generate wallets, get wallet details, send tx, specify stealth, etc. And I’m excited to keep adding as I go for a complete cli wallet + provisioning utility.

Create Wallet

❯ yarn bchctl --profile bastian wallet init
profile:        bastian
config file:    /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/config.json
state file:     /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/bastian/state.json
log file:       /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/bastian/events.ndjson
network:        chipnet
birthdayHeight: 0
address:        bchtest:qzq49scpjpr5vnzkd6d9a6nyw6uchm3cs569heay5f
paycode:        PM8TJW4zvYiRdigQNN1MnE2hxemQVBoiba3CvtoQW2QjAuy6dcU7XwW5guAvwwq4GHLcw7vp3Qa7ZuTNg1yMGBkdEGVDdS3NJyAUMKCRzH21VD3d5WVq

ℹ Tip: run "bchctl --profile bastian wallet show --all" to view pubkeys/hash160.


⚠️  save this mnemonic (unencrypted for now):
cost citizen button adult agree blind column change column around broad chief

and then subsequent details

❯ yarn bchctl --profile bastian wallet show --all
profile: bastian
address: bchtest:qzq49scpjpr5vnzkd6d9a6nyw6uchm3cs569heay5f
paycode: PM8TJW4zvYiRdigQNN1MnE2hxemQVBoiba3CvtoQW2QjAuy6dcU7XwW5guAvwwq4GHLcw7vp3Qa7ZuTNg1yMGBkdEGVDdS3NJyAUMKCRzH21VD3d5WVq
base.pub33Hex:     02d16b2d0a66ea3b8556ddc63011c0a83e9131ea16198d1a635388f92131975659
base.hash160:      8152c3019047464c566e9a5eea6476b98bee3885
paycode.pub33Hex:  02f1337472093bd52cb9f1e5e01c280b4293edbae15ec54ccf36c791a7bfd2e7f7
scan.pub33Hex:     02f1337472093bd52cb9f1e5e01c280b4293edbae15ec54ccf36c791a7bfd2e7f7
spend.pub33Hex:    02a00dd35c30f1fbe9bd6d8ac8e6561a9d33ab2ca37f6643c903beb8109b009ac4

but the pool template is my favorite innovation so far

❯ yarn bchctl --profile bastian pool init --shards 8  --fresh

[funding] Network: chipnet
[funding] Fund this base P2PKH address if you see "No funding UTXO available":
  - me (base P2PKH): bchtest:qzq49scpjpr5vnzkd6d9a6nyw6uchm3cs569heay5f

[funding] Notes:
  - Change may go to stealth (paycode-derived) P2PKH outputs.
  - External wallets won’t track those outputs.
  - The CLI can spend them IF they are recorded in the state file (stealthUtxos).
  - Keep reusing the same state file between runs.
[getPrevoutScriptAndValue] spk prefix: 76a91481
[init] funding outpoint: 3b76e68be35ac7162d5dd63a859e5c67e3c08dc3b0d136457ec2f4c8b55aea92:0
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b612062a36bbbb64f2bd9c1979e51c86d2c23932ed6ec1eb3b3bfc1fd0e86521fea68
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b61209915c366c939096858044d97e1f002af2731ae0de724e80c3d7e48176e0da29a
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b6120a5fe87136f831be7ab21f1930edb4d65e94ad219f070e9bfd1d4ce2bd62cb38e
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b6120170fc0e48e4108d11f87e298d3179cf06d163638f129c0e46fe29b9b3fb2040b
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b6120251f46f5346920b1868dd4560b00d33c2db26363aacd1bd2d870f4b1225b3f1a
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b61208c3d5470deb07d0b7dd86324ba71c49c40233f8bec2092c5248b1a4a01fd827a
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b6120441312fcb95ee714f2f79954774d40e351c97601f7f49d2c0e49771c466b91d0
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b6120d02daf6ad24233d05bb9c087682df622dba4f22bb1975abefd44f677580d3ea0
init txid: 7871eccbcea55174b29c877c110dad101802671cf6784d3498bbeaa5d1d2a344
shards: 8
fresh: true
state saved: /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/bastian/state.json (pool.state)

The pool serves as a state pool that is used to keep records of stealth tx, detached from identities using cashtokens / nfts. it’s deterministic and restoreable from mnemonic (pending open items, currently in development).

So now, Alice can send to Basitan’s paycode and Bastian can import that utxo to his state pool:

❯ yarn bchctl --profile alice send PM8TJW4zvYiRdigQNN1MnE2hxemQVBoiba3CvtoQW2QjAuy6dcU7XwW5guAvwwq4GHLcw7vp3Qa7ZuTNg1yMGBkdEGVDdS3NJyAUMKCRzH21VD3d5WVq 10000 --all --grind-max 2048
[getPrevoutScriptAndValue] spk prefix: 76a914c7
[getPrevoutScriptAndValue] spk prefix: 76a9145b
network:     chipnet
profile:     alice
amountSats:  10000
destType:    paycode
dest:        bchtest:qp25vpxadrsy6zc47tzpyqa2rmhmj9w8d5qjjsl2ew
destHash160: 554604dd68e04d0b15f2c41203aa1eefb915c76d
txid:        7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03
tx:          https://chipnet.chaingraph.cash/tx/7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03
grind:       yes
grindIndex:  14
grindMax:    2048
grindByte:   55
rawHex:      0100000001af7fc7271fe9d42640470146188be9c0900f4d18acfa1e18c6c763e2ec867989010000006441a527553937d6aab42292fad801431066915fbca1a4625cad878892cf37e414e1c1e41397ad200ea2121a516fd09322fa4a0e9b89f576bdde336f6e1898d5020941210281c258a02fa99bc1582a0e97dd6d5a2e85a3d6e848fd30dbcf1c5606f52d865dffffffff0210270000000000001976a914554604dd68e04d0b15f2c41203aa1eefb915c76d88acd02c9700000000001976a9145bb44f41d7a156645738c447aead03979f5b113588ac00000000

and bastian can scan (right now using the txid, auto scan will be refined in phase 3 with secure notes helping speed the process)

❯ yarn bchctl --profile bastian scan --txid 7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03 --update-state --all
candidateTxids(sample):
  7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03
network:        chipnet
profile:        bastian
tipHeight:      292239
startHeight:    0
endHeight:      292239
rpaPrefixHex:   (none)
txid:           7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03
candidates:     1
updateState:    yes
spendKey:       overridden (config mismatch; using derived)
scan: fetching raw tx 1/1... done
found:          1

outpoint:       7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03:0
valueSats:      10000
address:        bchtest:qp25vpxadrsy6zc47tzpyqa2rmhmj9w8d5qjjsl2ew
hash160Hex:     554604dd68e04d0b15f2c41203aa1eefb915c76d
senderPub33Hex: 0281c258a02fa99bc1582a0e97dd6d5a2e85a3d6e848fd30dbcf1c5606f52d865d
prevoutTxidHex: 897986ece263c7c6181efaac184d0f90c0e98b184601474026d4e91f27c77faf
prevoutN:       1
index:          14

stateFile:      //development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/bastian/state.json
stateUtxos:     1

and then bastian can import the rpa tx into this state pool

❯ yarn bchctl --profile bastian pool import 7ed73c1f3832982059e8e2d613f7e97e0ad514598bfa91a4ec25be72104c9e03:0

[funding] Network: chipnet
[funding] Fund this base P2PKH address if you see "No funding UTXO available":
  - me (base P2PKH): bchtest:qzq49scpjpr5vnzkd6d9a6nyw6uchm3cs569heay5f

[funding] Notes:
  - Change may go to stealth (paycode-derived) P2PKH outputs.
  - External wallets won’t track those outputs.
  - The CLI can spend them IF they are recorded in the state file (stealthUtxos).
  - Keep reusing the same state file between runs.
[getPrevoutScriptAndValue] spk prefix: 76a91455
[getPrevoutScriptAndValue] spk prefix: 76a91481
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: 76a91455
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b612072aee2cef3c5991cf7d0b828cde0973810999e36babf0356a4b098e70e861f07
import txid: c43df3b243d374df9571c34e6f3566883eeb92f2cdced8f327eb81d6064bc012
shard: 6
state saved: /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/bastian/state.json (pool.state)

This isn’t important to demonstrate because we are transacting in RPA. This is important because we are importing RPA transactions into sharded state pool. Assuming the inputs used in the RPA are detached from base wallet originally using cash fusion (another potential enhancement that may be included) we now have a solid infrastructure to import utxo’s, fully detached from wallet identities, into a state pool, where users are now able to transact in a zcash like secure notes transfer to be implemented in phase 3.

So right now, phase 2 has successfully laid the infrastructure to being fully iterating on confidential transaction flows where identity and amounts may be shielded in phase 3.

Exciting times!

When Bastian is ready to transact again, he simply withdraws from his pool.

Let’s grab Alice’s paycode (this utility allows saving multiple profiles as identities with independent key mgmt)

❯ yarn bchctl --profile alice status
profile(flag):    alice
currentProfile:   bastian
config:           /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/config.json
network:          chipnet
birthdayHeight:   276337
wallet:           ready
transparent:      bchtest:qpdmgn6p67s4vezh8rzy0t4dqwte7kc3x53ls5pxfc
paycode:          PM8TJWUKWWgeLpbNVCXvtE2WWsJ9ZBAf7nCTgZHLjUScePoPAFcYdfc7oP6uUChStEnQEL9YRDsNmtsgnPLFsZMhAiMpjNHrLSr28h8enjQQy1CJFZc8
state file:       /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/alice/state.json (exists)
events log:       /development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/alice/events.ndjson (missing)

and now Bastian simply withdraws directly from his state pool (detached from his base wallet) to Alice’s paycode

❯ yarn bchctl --profile bastian pool withdraw PM8TJWUKWWgeLpbNVCXvtE2WWsJ9ZBAf7nCTgZHLjUScePoPAFcYdfc7oP6uUChStEnQEL9YRDsNmtsgnPLFsZMhAiMpjNHrLSr28h8enjQQy1CJFZc8 5000 --shard 6 --require-shard

[funding] Network: chipnet
[funding] Fund this base P2PKH address if you see "No funding UTXO available":
  - me (base P2PKH): bchtest:qzq49scpjpr5vnzkd6d9a6nyw6uchm3cs569heay5f

[funding] Notes:
  - Change may go to stealth (paycode-derived) P2PKH outputs.
  - External wallets won’t track those outputs.
  - The CLI can spend them IF they are recorded in the state file (stealthUtxos).
  - Keep reusing the same state file between runs.
[getPrevoutScriptAndValue] spk prefix: ef92ea5a
[getPrevoutScriptAndValue] spk prefix: 76a91481
Using updated addTokenToScript with nft handling
Token Prefix (hex): ef92ea5ab5c8f4c27e4536d1b0c38dc0e3675c9e853ad65d2d16c75ae38be6763b61205cf65cb9618263db65808f9c83d5f6977d71c662780aa0473b074a9300d57e74
withdraw txid: 503fd990e3ab1afd19a7981607a8e52117e9204d08b6208e937c6e6132ccf1ee
state saved: //development/bastian/bitcoin-cash-stealth-demo/.bch-stealth/profiles/bastian/state.json (pool.state)

Next up, stealth change handling and then on to the UI!

4 Likes

Great work!

Could this also be used to disconnect a UTXO from a CashFusion TX within your own wallet?

The use case could be sending to an exchange or payment processor where they might be flagging CashFusion UTXO’s as suspicious.

No, this doesn’t “wash” or erase CashFusion history. On a UTXO chain, the ancestry of the inputs is still visible, so if an exchange/payment processor is flagging deposits based on CashFusion, simply sending those coins to a new address (even a paycode-derived stealth P2PKH) doesn’t magically make that history disappear.

What this flow does do is reduce identity linkage going forward:

  • payments to a paycode land in fresh, unlinkable P2PKH outputs (no address reuse),
  • and change routes to your self paycode so you’re not accidentally tying change back to your base/transparent identity.
3 Likes

Thanks @Bastian for sharing this progress, which started as exploring Silent Reusable Payment Addresses (SRPA) and now it has evolved into a full ZKP privacy system on BCH, covering both stealth identity and confidential transactions. That’s a massive scope expansion. BCH gets Monero-level privacy, but with an auditable supply, all enforced natively via CashVM without compromising chain transparency :fire: @bitjson is also exploring​​​​​​​​​​​​​​​​ zk prover.


Source

It seems Triton VM would be the closest candidate to power the proof verification layer for confidential transactions on BCH .​​​​​​​​​​​​​​​​

2 Likes