New CHIP: 2026-paths (the derivation type)

don’t do that.

Don’t delute the term “watch only” to mean something different than it means. It actually means that the wallet is incapable of doing anything but watching. It is a security thing where you are comfortable giving this watch only wallet to people you lack the trust in them to not spend it.

If you can’t have the ‘watch only’ security on RPA, then just be clear about that. A UI option means they can extract the private key from disk and spend your money.

@tom @robchank I’m working on an account/identity implementation for Selene-related services. With some experimentation, and taking this discussion into consideration, here’s what I think we should settle on:


For purposes of discussion, a wallet is a collection of a user’s keys, and a wallet app is a software app that manages wallets. These two terms can be used nearly-interchangeably.

Wallet apps derive wallet keys deterministically according to the widely-adopted BIP-44 standard, which defines derivation paths with grammar as such:

m'/<purpose>'/<coin_id>'/<account_id>'/<change>/<address_index>

Where ' denotes a hardened derivation path as defined in BIP-32.


A typical BCH wallet app will create its initial wallet using the m'/44'/145'/0' derivation path, which I will call the root derivation path.

For BCH wallets, we should generally consider the purpose and coin_id to be constant. Per BIP-44, purpose set to 44' indicates that we are adhering to the BIP-44 path grammar, and coin_id set to 145' indicates that we are deriving addresses for Bitcoin Cash and not some other blockchain.

We will note the account_id set to 0'. We will consider this path to be not only the root derivation path, but also the wallet’s default account path.

From the default account path, the wallet can derive receive and change addresses by setting change to 0 and 1, respectively. To prevent address reuse, we can increment the address_index for each change path. So the very first user-facing (receive) address would be derived from the path m'/44'/145'/0'/0/0, and the wallet will internally manage change addresses starting from m'/44'/145'/0'/1/0.


Earlier we noted the account_id. We must disambiguate the words account and identity.

For this discussion, an account will refer to a high-level concept similar to “user account” or a “bank account.” The funds and payment histories for each account are all isolated from each other.

A wallet app could present options to manage multiple accounts, each with their own name, but derived from a single seed. Where the default account has the account_id set to 0, we could create any arbitrary number of accounts by setting the account_id to any 0...n.

By these definitions, a single wallet created from root derivation path can contain multiple accounts, where each account derives addresses on multiple change paths.


For identity purposes, we will set the change path to 2. Thus we define the account identity path as m'/44'/145'/<account_id>/2/<identity_index>.

We will define the path at m'/44'/145'/0'/2/0 to be the default wallet identity.

The CashID authentication protocol is a simple challenge-response protocol that enables authentication to a service by signing a challenge message using a private key managed by a user’s wallet.

For privacy, each CashID address should be unique to the service it authenticates to, much like we discourage reuse of payment addresses. Thus, we consider each key derived from the account identity path to represent a single identity.

Two Ways to Handle Identity Derivation
  1. We could simply increment identity_index for each service, and track the metadata of which index corresponds to a service inside the wallet app. For wallet backups, the metadata must be stored, else the only way to recover this metadata is to brute force unlinked addresses against each known service until we learn the correspondence of each.

  2. We could derive the identity_index deterministically based on some canonical service_id, perhaps a hash of the authenticated CashID origin. I am currently leaning in this direction, but am open to discussion on the matter.


With the identity path clearly defined, we then maintain rationale for moving the RPA derivation path to m'/44'/145'/<account_id>'/3/0 for the scan key and m'/44'/145'/<account_id>'/3/1 for the spend key. If the user wishes to derive more reusable paycodes, the wallet should reserve adjacent pairs (2+3, 4+5, etc).

Hopefully this clarifies things for everyone involved, and hopefully what I wrote here aligns with everyone’s existing understanding. Please let me know if there’s anything here that should be ironed out further. :slight_smile:

3 Likes

Also for consideration: if we are able to deterministically know an identity_index for each service, then on the service’s subpaths we could derive things service-specific paycodes, or refund xpubs

This aligns perfectly with my view on this matter. Thank you for doing the write-up.

1 Like

Thanks @kzKallisti this is very comprehensive. I really like ECDH based addresses and discussions like this is the thing that move privacy forward on bitcoin cash i really wish these discussions happened long time ago. I have one question regarding Wizardconnect which can use a template for wallets handshake via extensions to define the workflow of RPA. So, i wonder here how do you think the workflow should be for BCH dapps that use RPA through WizardConnect?

RPA is separate from WizardConnect. In this design, I plan on using WizardConnect as a transport for CashID, and I plan on linking RPA with CashAccount.

So, CashAccount lookup will give you the user’s RPA for payments. The CashAccount is associated with the account as defined above. We are never logging in via CashID using the RPA or the default account path directly. We instead will always furnish a unique address for each service using the identity path for CashID purposes. The wallet app will communicate with the third-party app via WizardConnect in order to negotiate the login using the CashID spec.

I checked Gem Wallet’s current BCH implementation.

Gem uses the standard default path m/44'/145'/0'/0/0 , and derives/stores the account xpub at m/44'/145'/0' . So the current structure is compatible with the proposal’s default BCH account and first receive address.

However, Gem currently operates as a single-address BCH wallet. It does not increment receive-address indexes, use a separate .../1/<index> change branch, or derive additional account IDs. Change is returned to the same .../0/0 address, and the signer currently expects all UTXOs to belong to that one sender key.

Gem also does not currently use the proposed /2/* identity or /3/* RPA branches, so there is no path conflict with those reservations.

Supporting the fuller proposal would require path-aware key derivation and signing, address/index state, separate change addresses, and recovery discovery with an appropriate gap limit. Existing Gem BCH wallets at m/44'/145'/0'/0/0 would remain compatible because that is already the proposed default account’s first receive address.

From an implementation perspective, having a community registry for /2 , /3 , /7 , and other specialized branches would be useful before wallets start allocating these paths independently.

At this time, the only commonly used paths are the /0 and /1 “receive” and “change” paths.

The /7 path is used by the new WizardConnect protocol which is spreading quickly throughout the BCH ecosystem, but it’s probably unrealistic for us to expect a multi-coin wallet to support WizardConnect.

The /2 and /3 paths are currently non-standard, and this thread is seeking to actively define an agreed-upon standard. So far it seems everyone agrees on the usage of the /2 and /3 paths proposed in this thread.

Here and now is the time and place to standardize! Thanks for your participation :slight_smile:

BTW, your Bitcoin, Bitcoin Cash, and Litecoin users would all benefit from avoiding address reuse. It’s much better for user privacy. Please consider at least supporting /0 and /1 for “receive” and “change” addresses. The others are probably not necessary for Gem Wallet’s current posture.

3 Likes

Thanks, that distinction is helpful. Agreed that /0 and /1 are the most relevant parts for Gem Wallet’s current scope.

Moving from the current single-address model to indexed receive and change addresses would be more than a derivation-path adjustment: it would also require address-state tracking, mapping each UTXO to its signing path, recovery discovery with a gap limit, and continued monitoring of the existing /0/0 address.

For wallets migrating from a model where /0/0 was used for both receiving and change, is the expected approach to keep monitoring /0/0 indefinitely while allocating new receive addresses under /0/<index> and change addresses under /1/<index> ? It would be useful for the standard to document migration and recovery behavior for existing wallets as well.

I like to see the ‘change’ branch as addresses that the wallet is managing, whereas the receive (or main) branch (/0) is managed by the human.

In a wallet which does not re-use addresses, the change branch, that the wallet manages, are then addresses that can be safely thought as one time use. So when money gets deposited on that address and later removed, that address can then after a short time be removed from the list of addresses you monitor.

The recieve branch (/0) is generally seen as addresses that the human can give away. For instance in a QR code that is shown to a friend or a customer.
And that means there is no guarentees on it’s lifetime. It can be used once, it can also be used once a year. The wallet can’t know what the operator meant when they handed out that QR.

As such my thinking is that the /0 branch will basically be one that is monitored forever.

2 Likes

Yes, the expectation is that you monitor old addresses, but that obviously doesn’t scale;

Wallets like Selene, Flowee, and Electron Cash use some heuristic to determine if an address should still be watched.

In general, you can ignore all “change” addresses that have exactly two transactions in their history, as those are assumed to only ever be managed by wallet software and not humans, and they’re assumed to only be used once.

For “receive” addresses, I use a similar heuristic, while also checking if the address has >= 3 transactions - if so, I assume it could be some kind of static donation address that should be watched indefinitely.

So basically, if you suspect the user is actively reusing an address, you should watch that address.

1 Like

Thanks @tom and @kzKallisti, this clarifies the distinction.

For an existing Gem Wallet, /0/0 is already both user-facing and reused for change, so the conservative migration approach would be to classify that specific legacy address as permanently watched regardless of its transaction count.

Newly generated /1/<index> change addresses could then follow the wallet-managed, one-time-use lifecycle @tom described. New /0/<index> receive addresses would require longer-lived monitoring based on wallet metadata or a reuse heuristic, as @kzKallisti explained.

For seed-only recovery, the transaction-history heuristic seems useful after addresses have been discovered, although recovery would still need a defined gap-limit scan because the original wallet metadata may be unavailable.

This distinction between legacy addresses, receive addresses, and disposable change addresses would be valuable to document as migration guidance.

The “spec” gap limit according to BIP-44 is 20 addresses. When generating addresses, Selene Wallet and Electron Cash both maintain 20 empty receive addresses and 20 empty chain addresses at any given time.

With Selene I scan beyond well beyond the gap limit due to some early bugs where UTXOs were being deposited beyond the gap. Not 100% necessary, but helpful.

2 Likes