Replace placeholder 'bip352' / 'bip47_rpa' with the finalized extension names from the BCR post and BCH Stealth Protocol spec: - bip352 → bch_stealth_bip352 - bip47_rpa → rpa_bip47 Add spend_path / scan_path fields to each extension handshake object, pointing to the hardened gate paths where the wallet exports xpubs. Dapps derive the non-hardened /0 child locally. Add rpa_spend / rpa_scan path names alongside stealth_spend / stealth_scan in all examples. Update known extensions table with full path information and BCR reference links. Spec: https://bitcoincashresearch.org/t/ecdh-stealth-addresses-on-bitcoin-cash-implementation-code/1773/5 I have read the CLA Document and I hereby sign the CLA
207 lines
6.9 KiB
Markdown
207 lines
6.9 KiB
Markdown
# Extensions
|
|
|
|
The hdwalletv1 protocol supports optional extensions that let wallets and dapps negotiate
|
|
additional capabilities beyond the core sign-transaction flow. Extensions are backward-compatible:
|
|
existing wallets and dapps that don't know about extensions continue to work unchanged.
|
|
|
|
## How extensions work
|
|
|
|
Extensions use three mechanisms, all of which are optional and additive:
|
|
|
|
### 1. Session extensions field
|
|
|
|
Wallets advertise supported extensions in the `wallet_ready` handshake via an `extensions` field
|
|
on the `Hdwalletv1Session` object:
|
|
|
|
```typescript
|
|
interface Hdwalletv1Session {
|
|
paths: PathXpub[];
|
|
extensions?: Record<string, unknown>;
|
|
}
|
|
```
|
|
|
|
Each key in `extensions` is an extension name. Its **presence** indicates the wallet supports that
|
|
extension. The value carries extension-specific handshake data (derivation paths to the hardened
|
|
gate where the wallet exports xpubs), or `{}` if no data is needed.
|
|
|
|
Example:
|
|
```json
|
|
{
|
|
"paths": [
|
|
{ "name": "receive", "xpub": "xpub6..." },
|
|
{ "name": "change", "xpub": "xpub6..." },
|
|
{ "name": "defi", "xpub": "xpub6..." },
|
|
{ "name": "stealth_spend", "xpub": "xpub6..." },
|
|
{ "name": "stealth_scan", "xpub": "xpub6..." },
|
|
{ "name": "rpa_spend", "xpub": "xpub6..." },
|
|
{ "name": "rpa_scan", "xpub": "xpub6..." }
|
|
],
|
|
"extensions": {
|
|
"bch_stealth_bip352": {
|
|
"spend_path": "m/352'/145'/0'/0'",
|
|
"scan_path": "m/352'/145'/0'/1'"
|
|
},
|
|
"rpa_bip47": {
|
|
"spend_path": "m/47'/145'/0'/0'",
|
|
"scan_path": "m/47'/145'/0'/1'"
|
|
},
|
|
"decrypt": { "public_key": "02abc...", "scheme": "ecies" }
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Additional path names
|
|
|
|
`PathName` is an open string type. Wallets may include paths beyond the well-known
|
|
`receive`/`change`/`defi` set. Extension-defined paths are carried in the standard `paths` array:
|
|
|
|
```json
|
|
{
|
|
"paths": [
|
|
{ "name": "receive", "xpub": "xpub6..." },
|
|
{ "name": "change", "xpub": "xpub6..." },
|
|
{ "name": "stealth_spend", "xpub": "xpub6..." },
|
|
{ "name": "stealth_scan", "xpub": "xpub6..." },
|
|
{ "name": "rpa_spend", "xpub": "xpub6..." },
|
|
{ "name": "rpa_scan", "xpub": "xpub6..." }
|
|
],
|
|
"extensions": {
|
|
"bch_stealth_bip352": {
|
|
"spend_path": "m/352'/145'/0'/0'",
|
|
"scan_path": "m/352'/145'/0'/1'"
|
|
},
|
|
"rpa_bip47": {
|
|
"spend_path": "m/47'/145'/0'/0'",
|
|
"scan_path": "m/47'/145'/0'/1'"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Dapps should ignore path names they do not recognize. The standard pubkey derivation logic
|
|
(`DappPubkeyStateManager`) automatically skips unknown paths.
|
|
|
|
### 3. Custom message actions
|
|
|
|
Extensions may define new message action strings beyond the well-known set. Custom messages follow
|
|
the standard `ProtocolMessage` shape (`action` + `time`) and use the existing relay transport.
|
|
|
|
Convention for request/response operations:
|
|
|
|
```typescript
|
|
// Request (dapp → wallet):
|
|
{ action: "<operation>_request", sequence: number, time: number, ...params }
|
|
|
|
// Response (wallet → dapp):
|
|
{ action: "<operation>_response", sequence: number, time: number, ...result }
|
|
```
|
|
|
|
The `sequence` field ties responses to requests, matching the pattern used by
|
|
`sign_transaction_request`/`sign_transaction_response`.
|
|
|
|
**Wallet side**: custom messages are emitted via the `"message"` event on
|
|
`WalletConnectionManager`:
|
|
|
|
```typescript
|
|
manager.on("message", (connectionId, msg) => {
|
|
if (msg.action === "decrypt_request") {
|
|
// handle decrypt request
|
|
}
|
|
});
|
|
```
|
|
|
|
**Dapp side**: all messages (including custom ones) are emitted via the `"messagereceived"` event
|
|
on `DappConnectionManager`:
|
|
|
|
```typescript
|
|
manager.on("messagereceived", (msg) => {
|
|
if (msg.action === "decrypt_response") {
|
|
// handle decrypt response
|
|
}
|
|
});
|
|
```
|
|
|
|
## Implementing an extension (wallet side)
|
|
|
|
Wallets advertise extensions by implementing optional methods on `WalletAdapter`:
|
|
|
|
```typescript
|
|
interface WalletAdapter {
|
|
// ... core methods ...
|
|
|
|
/** Additional paths to include in the session (e.g. stealth_scan). */
|
|
getAdditionalPaths?(): PathXpub[];
|
|
|
|
/** Extension data for the session handshake. */
|
|
getExtensions?(): Record<string, unknown>;
|
|
}
|
|
```
|
|
|
|
Example:
|
|
```typescript
|
|
const adapter: WalletAdapter = {
|
|
// ... core implementation ...
|
|
|
|
getAdditionalPaths() {
|
|
return [
|
|
{ name: "stealth_spend", xpub: deriveXpub("m/352'/145'/0'/0'") },
|
|
{ name: "stealth_scan", xpub: deriveXpub("m/352'/145'/0'/1'") },
|
|
{ name: "rpa_spend", xpub: deriveXpub("m/47'/145'/0'/0'") },
|
|
{ name: "rpa_scan", xpub: deriveXpub("m/47'/145'/0'/1'") },
|
|
];
|
|
},
|
|
|
|
getExtensions() {
|
|
return {
|
|
"bch_stealth_bip352": {
|
|
"spend_path": "m/352'/145'/0'/0'",
|
|
"scan_path": "m/352'/145'/0'/1'",
|
|
},
|
|
"rpa_bip47": {
|
|
"spend_path": "m/47'/145'/0'/0'",
|
|
"scan_path": "m/47'/145'/0'/1'",
|
|
},
|
|
};
|
|
},
|
|
};
|
|
```
|
|
|
|
Wallets that don't implement these methods produce the same session as before (receive/change/defi
|
|
paths only, no extensions field).
|
|
|
|
## Discovering extensions (dapp side)
|
|
|
|
Dapps check for extension support after receiving `wallet_ready`:
|
|
|
|
```typescript
|
|
manager.on("walletready", (msg) => {
|
|
const session = msg.session["hdwalletv1"] as Hdwalletv1Session;
|
|
|
|
if (session.extensions?.bch_stealth_bip352) {
|
|
const scanPath = session.paths.find(p => p.name === "stealth_scan");
|
|
const spendPath = session.paths.find(p => p.name === "stealth_spend");
|
|
// enable stealth address features
|
|
} else {
|
|
// show: "Stealth payments require a wallet that supports BCH Stealth (BIP352)"
|
|
}
|
|
|
|
if (session.extensions?.rpa_bip47) {
|
|
const rpaSpend = session.paths.find(p => p.name === "rpa_spend");
|
|
const rpaScan = session.paths.find(p => p.name === "rpa_scan");
|
|
// enable RPA features
|
|
}
|
|
});
|
|
```
|
|
|
|
Dapps should degrade gracefully when an extension is absent. If a feature requires an extension the
|
|
wallet doesn't support, inform the user rather than failing silently.
|
|
|
|
## Known extensions
|
|
|
|
| Extension name | Path names | Hardened gate paths | Purpose | Status |
|
|
|---------------|-----------|---------------------|---------|--------|
|
|
| `bch_stealth_bip352` | `stealth_spend`, `stealth_scan` | `m/352'/145'/0'/0'`, `m/352'/145'/0'/1'` | BCH stealth addresses (BIP352 structure). Wallet exports xpubs at hardened gates; dapp derives `/0` child locally. | Standard — [BCR post](https://bitcoincashresearch.org/t/ecdh-stealth-addresses-on-bitcoin-cash-implementation-code/1773/5) |
|
|
| `rpa_bip47` | `rpa_spend`, `rpa_scan` | `m/47'/145'/0'/0'`, `m/47'/145'/0'/1'` | BIP47 reusable payment addresses. Wallet exports xpubs at hardened gates; dapp derives `/0` child locally. | Standard — [BCR post](https://bitcoincashresearch.org/t/ecdh-stealth-addresses-on-bitcoin-cash-implementation-code/1773/5) |
|
|
| `decrypt` | — | — | Dapp-side encrypted storage. Wallet provides a public key; dapp encrypts data for storage and sends `decrypt_request` messages when the data is needed. | Proposed |
|
|
|
|
See the discussions and specifications for each extension as they are formalized.
|