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.