WizardConnect/docs/extensions.md

214 lines
7.3 KiB
Markdown
Raw Permalink Normal View History

# Extensions
This document covers `hdwalletv1` protocol-level extensions — optional capabilities that extend
the application protocol (extra path names, custom message actions, per-wallet features). For
transport-level capabilities that apply below the application protocol regardless of which
protocol is in use (chunking, future: compression), see
[transport.md § Transport-level extensions](transport.md#transport-level-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.