SRPA-style stealth identities, Stealth change, and covenants using standard BCH transactions.
Digital privacy is becoming a requirement, not a luxury. As larger and more recognizable actors move value over public blockchains, a permanent, searchable trail of who paid whom — and how much — becomes a real liability.
Bitcoin Cash is uniquely positioned to address this:
- UTXO model,
- Low fees and fast confirmation,
- CashTokens and covenant support,
- Roadmap space for more advanced ZK tooling in the future.
ABLA’s Silent Reusable Payment Addresses (SRPA) proposal on BitcoinCashResearch sketches a path to Monero-style identity privacy on BCH without giving up auditable supply or introducing new script types. The core idea:
- A static “stealth identity” (SRPA key / paycode),
- One-time addresses derived via ECDH from that identity,
- Fusion-friendly behavior,
- All wrapped in standard P2PKH/P2SH transaction formats.
This post documents a Phase-1 working demo on Bitcoin Cash testnet (chipnet) that follows that model:
- Implements SRPA-style paycodes and one-time ECDH-derived addresses,
- Uses covenants + CashTokens to enforce correct spending,
- Routes both sides’ change back into SRPA space (Stealth),
- Produces standard-looking BCH transactions (P2PKH / P2SH + CashTokens).
The code currently lives in a private repo; the goal of this write-up is to give the community a clear technical narrative for what’s already running and set the stage for open-sourcing and funding future work.
1. Design goals
Phase-1 was built around five concrete goals:
-
Static, reusable SRPA identity
Each wallet exposes a paycode: a long-lived stealth identity derived from a static keypair. It can live in a QR code, URI, profile, or contact entry. -
One-time addresses with standard scripts
Payments land on fresh P2PKH addresses derived via ECDH from the paycode and an ephemeral key (BIP352-inspired). On-chain, they are indistinguishable from normal BCH addresses. -
Self-stealth change (Stealth)
Both the receiver and the sender’s change return to SRPA-derived addresses, so a wallet can “live in SRPA space” continuously. -
Covenant-enforced correctness
A CashTokens-backed covenant binds:- A hash of a Sigma proof (off-chain),
- A Pedersen commitment to the amount (in the NFT commitment),
- A stealth pubkey hash that only the intended spender can satisfy.
-
No consensus changes
Everything uses the existing BCH VM:- P2PKH / P2SH scripts,
- CashTokens prefix for token category + NFT commitment,
- Standard transaction and validation rules.
2. What the Phase-1 demo actually does
Running it walks through three core chipnet transactions between Alice and Bob:
- Alice → covenant (funding, SRPA receive, self-stealth change)
- Bob → Alice SRPA + Bob SRPA change (covenant redeem)
- Alice SRPA → Alice base wallet (closing the loop)
2.1 Wallets & paycodes (static SRPA identities)
The script begins by creating two wallets and exporting their paycodes:
--- Obtaining Alice Wallet --- Alice Pub: 0281c258... Alice Address: bchtest:qpdmgn6... --- Obtaining Bob Wallet --- Bob Pub: 022dddb2... Bob Address: bchtest:qps6dfu... --- Generating Bob's Paycode --- ✅ Generated valid compressed pubKey (hex): 022dddb2... Bob Paycode: PM8TJLg6...
A paycode is a base58check payload that includes:
- A compressed secp256k1 public key (33 bytes),
- A chain code (32 bytes),
- Version/flags bytes and padding.
The only thing the sender needs is this paycode; the receiver doesn’t have to hand out a new address for every payment.
2.2 One-time SRPA addresses via ECDH
To pay Bob, Alice:
- Extracts Bob’s public key from the paycode.
- Generates an ephemeral keypair.
- Derives a one-time P2PKH address via ECDH.
Conceptually:
// conceptual, trimmed
function srpaDeriveAddress(ephemPriv, payPub) {
const shared = ecdh(payPub, ephemPriv); // shared = payPub * ephemPriv
const stealthHash20 = hash160(
sha256( concat(shared, utf8("srpa")) ) // domain separation: "srpa"
);
return p2pkhFromHash160(stealthHash20); // standard P2PKH script
}
Log excerpt:
--- Alice Derives SRPA One-Time Address Deterministically --- ✅ Extracted Bob Public Key (hex): 022dddb2... 🔑 Generated Alice Ephemeral Public Key (hex): 024c2816... 🎯 Derived SRPA Stealth Address: bchtest:qrzthhwyltvvswha0v72wa55jcgax2x4rv25x63sd5
On-chain, that output is just P2PKH to bchtest:qrzthh.... There’s no visible hint that it’s tied to a paycode.
2.3 Funding the covenant (Alice → covenant, Alice SRPA change)
Alice then:
- Optionally consolidates her UTXOs,
- Creates a CashTokens token category,
- Generates a Sigma range proof off-chain and commits only its hash on-chain,
- Encodes a Pedersen commitment into the NFT commitment,
- Sends funds to a covenant P2SH output, and
- Crucially, routes her change to a self-stealth SRPA address.
From the log:
NFT commitment (Pedersen C=vH+rG): 02f1d467... Token Prefix (hex): ef89bb55... 🔁 Alice self-change SRPA addr: bchtest:qrslqxxlasp08gaxcm6a6nt4grfzy7w9agstvzujtg ✅ Alice funded covenant TX: a66ce7966b20...
So even before Bob touches the covenant, Alice’s side is already operating in Stealth Mode: change back to SRPA, not to a static HD address.
2.4 Bob redeems: covenant → Alice SRPA + Bob SRPA change
When Bob redeems the covenant UTXO, he:
-
Decrypts the amount from the encrypted payload:
Decrypted amount: 100000
-
Re-derives the same proof hash and Pedersen commitment off-chain, verifying they match what the covenant expects.
-
Derives a fresh SRPA address for Alice using her paycode and the covenant context:
🔁 Bob → Alice SRPA address: bchtest:qq57d8j4ycydcjpj8sxqn22cdxs8w26f0yvgqud5t2
-
Derives his own SRPA change address (Stealth) using his paycode:
💱 Bob SRPA change address: bchtest:qppkd8lyxdew9fw7p9ghcvdtlsssdwp46vjat93hdh
The core of Stealth for Bob looks like this (simplified, but representative):
/* Stealth: derive Bob's SRPA change output (if bob.paycode is present) */
let bobChangeScript = null;
if (change >= DUST) {
if (bob.paycode) {
const bobPayPub = extractPubKeyFromPaycode(bob.paycode);
const bobChangeEphemPriv = sha256(concat(
bobPayPub,
uint64le(change),
hexToBytes(srpaUtxo.tx_hash),
uint64le(srpaUtxo.tx_pos)
));
const bobChangeSrpa = srpaDeriveAddress(bobChangeEphemPriv, bobPayPub);
let bobStealthAddr, bobStealthHash;
if (typeof bobChangeSrpa === 'string') {
bobStealthAddr = bobChangeSrpa;
bobStealthHash = getHash160FromAddress(bobStealthAddr);
} else {
bobStealthAddr = bobChangeSrpa.address;
bobStealthHash = bobChangeSrpa.stealthHash20;
}
console.log('💱 Bob SRPA change address:', bobStealthAddr);
console.log('💱 Bob SRPA change hash160:', bytesToHex(bobStealthHash));
bobChangeScript = getP2PKHScript(bobStealthHash);
} else {
// Fallback: classic HD change
bobChangeScript = getP2PKHScript(bob.hash160);
}
}
if (change >= DUST) {
tx.outputs.push({ value: change, scriptPubKey: bobChangeScript });
}
From a chain-analysis perspective:
- Output 0 is a tokenized P2PKH to Alice’s SRPA address,
- Output 1 is P2PKH to Bob’s SRPA change address.
Both outputs are standard P2PKH scripts; there is no “home” HD address that stands out as change.
2.5 Closing the SRPA loop
The demo then proves that Alice can re-derive the same stealth key and spend back to her original base wallet:
--- Closing the SRPA loop --- Alice derived stealth pub (hex): 02e6a5d1... HASH160(derived Alice stealth pub): 29e69e55... HASH160 from vout0 script: 29e69e55... ✅ Closed loop: Alice’s derived stealth key matches the SRPA output she received. ✅ Alice SRPA → source wallet txid: db40e6d1ab9ba0943d16126b614c97fe7848248d5050155d71264ed5580277d2
End-to-end, we see:
Base wallet → SRPA → covenant → SRPA → base wallet
All using standard P2PKH/P2SH + CashTokens transactions.
2.6 How SRPA and the covenant fit together
It’s worth calling out that SRPA and the covenant are logically separate pieces:
- The SRPA layer is what gives you reusable stealth identities and one-time receive addresses:
- Static paycodes,
- ECDH-derived P2PKH outputs,
- Mode B change that keeps coins inside SRPA space.
- The covenant layer is what gives you on-chain enforcement and anchoring:
- It checks that the spender is the intended party,
- That the amount matches what was committed,
- And that the NFT commitment and proof hash line up.
In other words, you can have a perfectly valid SRPA-style payment without a covenant. The sender and receiver can still use paycodes + ECDH to derive one-time P2PKH addresses and route change to SRPA-derived self-addresses.
The reason the covenant matters in this demo is forward-looking:
- It proves an SRPA address can successfully interact with a covenant with Bob signing under the SRPA
- It anchors a Pedersen commitment and a proof hash directly to the UTXO on-chain.
- That anchoring pattern is exactly what we need later for ZKPs and fully shielded “notes”:
- Prove things about amounts and ownership off-chain,
- Commit only a small fingerprint of that proof on-chain,
- Let the covenant enforce that the proof and outputs actually match.
So:
- SRPA ≈ stealth identity + one-time addresses + Mode B change
- Covenant ≈ enforcement + commitment anchor for future ZKPs
Phase-1 uses both so we can demonstrate SRPA today, while already laying the groundwork for more advanced, ZK-backed privacy in later phases.
3. Why we didn’t build full chain scanning (and why that’s okay)
What’s really happening:
- In the demo, the
txidis known in a closed loop environment. - That lets the wallet (or script) fetch the transaction directly, parse the outputs, and derive the SRPA keys needed to spend.
- For a developer/demo context, full wallet-side chain scanning is not necessary to prove that:
- SRPA derivation works,
- The covenant correctly binds to the stealth pubkey and NFT,
- Stealth change routing behaves as expected.
From a spec and roadmap perspective, we can say:
-
Today (Phase-1)
- Discovery is out-of-band: users (or higher-level software) share a
txidor short code privately (e2e chat or Nostr relays). - That’s sufficient to validate the cryptography, covenant behavior, and transaction shape.
- Discovery is out-of-band: users (or higher-level software) share a
-
Future (SRPA wallets / VM 2026 / ZK-enabled phases)
- Replace ad-hoc
txidsharing with either:- BIP352-style SRPA scanning, or
- A ZKP-based inbox / discovery mechanism, where:
- The receiver proves knowledge of a secret without revealing which output they own, or
- The transaction includes a ZK proof that “this output belongs to someone with paycode X” without leaking linkable metadata.
- Replace ad-hoc
The important takeaway for the community:
The cryptographic core — SRPA + covenants + Stealth change — is already demonstrated.
The remaining question is how wallets learn about their outputs, and that can be improved later (SRPA scanning or ZK discovery) without changing the underlying on-chain format.
4. Where we are, and what’s next
Right now, we can say:
-
We have stealth transactions per the ABLA SRPA model:
- One-time addresses derived via ECDH from static paycodes.
- Stealth change routing to self-stealth addresses for both sender and receiver.
- Covenant-enforced correctness (amount, NFT, key-hash).
-
On-chain, everything looks like a normal BCH + CashTokens transaction:
- No stealth flags, no exotic script types, no new opcodes.
-
The caveats:
- Wallets currently rely on out-of-band
txidsharing to discover incoming SRPA outputs in this demo. - That’s acceptable for a prototype and for certain application-layer use cases (e.g., chat-based payments, invoices, or coordinated flows).
- No curent wallet logic to track stealth wallets, so Alice spends back from stealth to source to prevent lost funds.
- Wallets currently rely on out-of-band
-
Clear path forward:
- Plug this SRPA logic into CashFusion to get receive → fuse → send cycles that live entirely in SRPA space.
- Introduce SRPA scanning or ZKP-based discovery in future phases so that:
- Users can restore from a seed & paycode,
- The wallet can automatically find all SRPA outputs,
- All without breaking compatibility or changing the on-chain format.
If you’re a wallet or protocol developer who wants to experiment:
- Start by implementing paycode +
srpaDeriveAddress. - Add Stealth change so all user-visible coins remain in SRPA space.
- Wire SRPA outputs into your CashFusion UTXO pool and treat them like any other P2PKH.
- Later, add SRPA scanning or ZKP-style discovery to complete the user experience.
This demo shows that stealth identity + strong on-chain anonymity patterns on BCH are not theoretical—they are achievable with incremental, wallet-level upgrades and no consensus changes.
5. How to support this work (and unlock the full repo)
Right now, the Phase-1 demo lives in a private repository while I:
- Stabilize and document the code,
- Design the path toward more advanced SRPA discovery and amount-hiding.
I want this to become a community asset, not a one-off experiment that disappears.
To make that sustainable, I’m running a small funding campaign to:
-
Open-source the Phase-1 repo under a permissive license, including:
- Full covenant implementation,
- SRPA Mode A and Stealth send/receive logic,
- Demo scripts and comments that match the narrative in this post.
-
Fund future research and implementation, focused on:
- Better SRPA discovery (so wallets don’t rely on out-of-band
txidsharing), - Stronger privacy guarantees (e.g. amount-hiding) that remain compatible with BCH’s auditable supply,
- Clean wallet integrations that keep SRPA + CashFusion fully interoperable.
- Better SRPA discovery (so wallets don’t rely on out-of-band
What I’m committing to
If the campaign is successfully funded, I will:
- Publish the Phase-1 codebase and documentation,
- Maintain a public roadmap for the next phases (discovery, fusion, and ZK layers),
- Keep the work aligned with BCH ecosystem needs (wallets, CashFusion, node implementations).
If funding falls short, I still intend to:
- Share the concepts, specs, and reference notes,
- Look for alternative ways to bring the implementation to the community—just at a slower pace and with fewer guarantees on completeness.
In other words:
Funding this work is not paying to remove a paywall on knowledge.
The ideas and specs will continue to be shared. Your support buys time and focus to turn this into a robust, well-tested, production-ready tool the whole BCH ecosystem can build on.
How to contribute
If you’d like to support:
- Visit: Phase 1 Campaign
- Or reach out directly if you’re a wallet, exchange, or infrastructure project interested in:
- Sponsorship,
- Co-designing integrations,
- Commissioning specific features (e.g., wallet plugin, server infra, docs).
If financial support isn’t an option, reviewing the design, or trying the prototype once it’s public already helps a lot.
The vision is simple:
A Bitcoin Cash wallet that can live entirely in SRPA space — receive → fuse → send — while remaining 100% compatible with today’s network.
If that’s a future you want to see, any form of support helps move it from demo to reality.
If you’d like to support more of this kind of research and engineering, you can send a tip in Bitcoin Cash.
Scan to donate in Bitcoin Cash
bitcoincash:qr399w3awgzgf86520tajj9qjsf8jnwtmurfp9gymc
Every bit helps carve out time for the unglamorous parts of this work: writing specs, tightening scripts, building reference implementations, and iterating toward a privacy stack that’s actually deployable in wallets instead of just living in whitepapers.