# Zcash Version Negotiation
This document describes how Zcash wallets and Keystone hardware wallets exchange firmware version information, enabling wallets to gate features based on device capabilities.
## Overview
Today the keystone does not report its firmware version to the wallet.
We update Keystone to reports its firmware version to wallets through two independent signals:
1. **At pairing time** via the `ZcashAccounts` QR code (a `deviceVersion` field in the CBOR payload)
2. **On every signed transaction** via `global.proprietary["keystone:fw_version"]` in the signed PCZT
Both signals are optional and backwards compatible. Old firmware omits them, and old wallets ignore them.
## Signal 1: Pairing QR (`ZcashAccounts`)
When a user connects their Keystone to a wallet by scanning the accounts QR code, the `ZcashAccounts` payload now includes an optional `deviceVersion` field. The QR uses the [Uniform Resource (UR)](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md) encoding, which wraps [CBOR (Concise Binary Object Representation)](https://cbor.io/) data into animated multi-frame QR codes for camera-based transmission.
### CBOR Map
| Key | Field | Type | Required |
| --- | ------------------ | ----- | -------- |
| 1 | `seed_fingerprint` | bytes | Yes |
| 2 | `accounts` | array | Yes |
| 3 | `device_version` | text | No |
This mirrors the existing `CryptoMultiAccounts` type used by other chains (Ethereum, Solana, etc.), which already carries `device`, `deviceId`, and `deviceVersion` fields at CBOR keys 3, 4, and 5 respectively.
TODO: Kris do you have an opinion of if we should add all 5.
### Example
A Keystone running firmware 12.4.0 produces:
```
{
1: h'd1d1d1...d1d1d1', // seed_fingerprint
2: [40001(...)], // accounts (tagged UFVKs)
3: "2.4.0-cypherpunk" // device_version
}
```
### Backwards Compatibility
| Scenario | Behavior |
| ------------------------- | --------------------------------------------------------------- |
| New Keystone + old wallet | Wallet ignores unknown CBOR key 3. Pairing works normally. |
| Old Keystone + new wallet | Key 3 is absent. Wallet sees `nil` and treats device as legacy. |
## Signal 2: Signed PCZT (`global.proprietary`)
Every signed PCZT response from Keystone carries a firmware version stamp in the PCZT's `global.proprietary` map.
### Proprietary Entry
| Key | Value | Encoding |
| --------------------- | ----------------------- | ----------- |
| `keystone:fw_version` | `[major, minor, build]` | 3 raw bytes |
The version triple matches `SOFTWARE_VERSION_MAJOR`, `SOFTWARE_VERSION_MINOR`, and `SOFTWARE_VERSION_BUILD` from the firmware's `version.h`.
TODO: Check on non cipherpunk
### Signing Flow
```
Wallet Keystone
| |
| ---- unsigned PCZT (QR) -------> |
| | signs transaction
| | stamps global.proprietary["keystone:fw_version"]
| | redacts witness data
| <--- signed PCZT (QR) ---------- |
| |
| reads fw_version from response |
| caches / evaluates policy |
```
The stamp is applied via the PCZT `Updater` role after signing but before the `Redactor` strips witness data. The `Redactor` does not touch `global`, so the stamp survives into the returned bytes.
### Backwards Compatibility
| Scenario | Behavior |
| ------------------------- | ----------------------------------------------------------------------- |
| New Keystone + old wallet | Wallet ignores unknown proprietary keys. Transaction proceeds normally. |
| Old Keystone + new wallet | `keystone:fw_version` is absent. Wallet treats device as legacy. |
## Wallet Behavior
The firmware does not enforce any version policy. It reports its version; the wallet decides what to do with it.
### Caching
Wallets should cache the detected firmware version, keyed by the device's `seedFingerprint`:
- **On pairing:** read `deviceVersion` from the `ZcashAccounts` QR and cache it
- **On each signed transaction:** read `keystone:fw_version` from the PCZT response and update the cache (detects firmware upgrades between pairing and signing)
Once cached, the wallet knows the device's firmware version without needing another round-trip.
### Future: Quantum-Recoverable Note Gating
When quantum-recoverable (QR) notes are introduced to the Zcash protocol, the cached firmware version enables wallets to gate QR-note spends on firmware capability:
- **Transaction does not spend QR notes:** proceed regardless of firmware version. Optionally warn if the device is legacy.
- **Transaction spends QR notes:** require firmware at or above the QR-capable version. Block the transaction if the device is too old or unknown.
The assumption is that firmware versions that support version negotiation will also support QR note signing.
## PRs
### QR connection passing firmware version
- `device_version` in `ZcashAccounts` CBOR: https://github.com/KeystoneHQ/keystone-sdk-rust/pull/127
- `deviceVersion` in Swift model: https://github.com/KeystoneHQ/keystone-sdk-ios/pull/39
- Firmware passes version in pairing QR: https://github.com/KeystoneHQ/keystone3-firmware/pull/2138
### Firmware version passed with signed PCZT
- Firmware version stamp on signed PCZTs: https://github.com/KeystoneHQ/keystone3-firmware/pull/2134
## Figjam Diagram
A basic [figjam flow chart](https://www.figma.com/board/DNYPvFwUqO94u9JgRwlU7T/Keystone-Wallet-Version-Negotiation?node-id=1-2&t=hLD1Tw9qTeSSddV2-1) that is a high level overview of what was discussed above:

## Figma Design
A rough draft of the above flow chart designed for zodl:
https://www.figma.com/design/XraEBDVrZO3wEMx6qzQhOM/Draft-QR-Flows?node-id=0-1&t=OctuEZs2drwjjWxs-1
## FAQ
### Why no build-flavor field?
Flavor is deliberately omitted. A firmware that can emit the pairing UR or a signed PCZT must already have app_zcash compiled in, so the artifact's existence is itself the capability signal. An explicit variant byte would be a redundant check (i.e. to confirm we are on the Cipherpunk firmware varient)