Skip to content

Commit bf90b48

Browse files
committed
feat: add Zcash chain support with PCZT signing and unified addresses
Add Zcash as the first privacy-preserving chain in OWS, with full shielded transaction support via the PCZT format. Chain registration: - ChainType::Zcash with CAIP-2 namespace, coin type 133 - Default lightwalletd endpoints (zec.rocks mainnet/testnet) Wallet creation (ows wallet create): - ZIP-32 key derivation from BIP-39 seed (needs_raw_seed trait extension) - Unified address with Orchard + Sapling receivers, shielded by default PCZT signing (ows sign tx --chain zcash): - Orchard spend authorization (RedPallas) - Sapling spend authorization (Jubjub) - Transparent spend authorization (secp256k1) - Skips non-matching/dummy actions per standard Orchard builder behavior - Key material zeroized after use Sign + broadcast (ows sign send-tx --chain zcash): - PCZT finalization via TransactionExtractor - Broadcast via lightwalletd gRPC with TLS Feature-gated behind zcash-shielded (enabled by default in CLI). Without the feature, falls back to transparent-only t-address support. Dependencies: zcash_keys, pczt, orchard, sapling-crypto, zcash_transparent, zcash_protocol, zcash_primitives (all from librustzcash, maintained by ZODL). Tests: 27 Zcash-specific tests (15 signer unit tests + 12 library integration tests). All 465 workspace tests pass. Docs: zcash-guide.md covering wallet creation, PCZT pipeline, security model, configuration, and end-to-end workflow.
1 parent d1abbe1 commit bf90b48

18 files changed

Lines changed: 2572 additions & 79 deletions

File tree

CONTRIBUTING.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,35 @@ cd bindings/python && maturin develop --release
3030
### Running Tests
3131

3232
```bash
33-
# Rust tests
33+
# Rust tests (all features, including Zcash shielded support)
34+
cd ows && cargo test --workspace --features zcash-shielded
35+
36+
# Rust tests (without Zcash shielded — faster, smaller dependency tree)
3437
cd ows && cargo test --workspace
3538

3639
# Node tests
3740
cd bindings/node && npm test
3841
```
3942

43+
### Zcash Development
44+
45+
Zcash shielded support (`zcash-shielded` feature) is enabled by default in the CLI but optional in the library crates. When developing Zcash-specific code:
46+
47+
```bash
48+
# Build with Zcash shielded support
49+
cd ows && cargo build --workspace --features zcash-shielded
50+
51+
# Run only Zcash tests
52+
cd ows && cargo test --features zcash-shielded -- zcash
53+
54+
# Clippy with Zcash features
55+
cd ows && cargo clippy --workspace --features zcash-shielded -- -D warnings
56+
```
57+
58+
The `zcash-shielded` feature adds dependencies on `zcash_keys`, `pczt`, `orchard`, and `sapling-crypto` from the [librustzcash](https://github.com/zcash/librustzcash) ecosystem. These are heavier than the base OWS dependencies — expect longer initial compile times.
59+
60+
See [docs/zcash-guide.md](docs/zcash-guide.md) for architecture and usage details.
61+
4062
### Code Formatting
4163

4264
```bash

docs/07-supported-chains.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type AssetId = `${ChainId}:${string}`;
2121
// e.g. "eip155:8453:native" (ETH on Base)
2222
```
2323

24-
The `native` token refers to the chain's native currency (ETH, SOL, SUI, BTC, ATOM, TRX, TON, etc.).
24+
The `native` token refers to the chain's native currency (ETH, SOL, SUI, BTC, ATOM, TRX, TON, ZEC, etc.).
2525

2626
## Chain Families
2727

@@ -38,6 +38,7 @@ OWS groups chains into families that share a cryptographic curve and address der
3838
| Sui | ed25519 | 784 | `m/44'/784'/{index}'/0'/0'` | `0x` + BLAKE2b-256 hex (32 bytes) | `sui` |
3939
| Spark | secp256k1 | 8797555 | `m/84'/0'/0'/0/{index}` | `spark:` + compressed pubkey hex | `spark` |
4040
| Filecoin | secp256k1 | 461 | `m/44'/461'/0'/0/{index}` | `f1` + base32(blake2b-160) | `fil` |
41+
| Zcash | secp256k1 / ZIP-32 | 133 | ZIP-32 (from raw seed) | Unified address (`u1...`) | `zcash` |
4142

4243
## Known Networks
4344

@@ -67,6 +68,7 @@ Each network has a canonical chain identifier. Endpoint discovery and transport
6768
| Sui | `sui:mainnet` |
6869
| Spark | `spark:mainnet` |
6970
| Filecoin | `fil:mainnet` |
71+
| Zcash | `zcash:mainnet` |
7072

7173
Implementations MAY ship convenience endpoint defaults, but those defaults are deployment choices rather than OWS interoperability requirements.
7274

@@ -90,6 +92,7 @@ ton → ton:mainnet
9092
sui → sui:mainnet
9193
spark → spark:mainnet
9294
filecoin → fil:mainnet
95+
zcash → zcash:mainnet
9396
```
9497

9598
Aliases MUST be resolved to full CAIP-2 identifiers before any processing. They MUST NOT appear in wallet files, policy files, or audit logs.
@@ -112,11 +115,14 @@ Master Seed (512 bits via PBKDF2)
112115
├── m/44'/607'/0' → TON Account 0
113116
├── m/44'/784'/0'/0'/0' → Sui Account 0
114117
├── m/84'/0'/0'/0/0 → Spark Account 0
115-
└── m/44'/461'/0'/0/0 → Filecoin Account 0
118+
├── m/44'/461'/0'/0/0 → Filecoin Account 0
119+
└── ZIP-32(seed, 0) → Zcash Account 0 (unified address)
116120
```
117121

118122
A single mnemonic derives accounts across all supported chains. The wallet file stores the encrypted mnemonic; the signer derives the appropriate private key using each chain's coin type and derivation path.
119123

124+
> **Note on Zcash:** Zcash unified addresses use ZIP-32 derivation, which operates on the raw BIP-39 seed rather than a BIP-32 derived key. The signer signals this via `needs_raw_seed() → true`, and the wallet layer passes the full 64-byte seed instead of a 32-byte derived key. See [Zcash Integration Guide](zcash-guide.md) for details.
125+
120126
## Adding a New Chain
121127

122128
1. Define a canonical chain identifier, preferably using CAIP-2.

docs/zcash-guide.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Zcash Integration Guide
2+
3+
Zcash is the first privacy-preserving chain in OWS. It supports shielded transactions via the PCZT (Partially Created Zcash Transaction) format, giving agents and wallets a way to hold and spend ZEC without exposing transaction metadata.
4+
5+
## How Zcash differs from other OWS chains
6+
7+
Every other chain in OWS follows the same pattern: derive a key via BIP-44, hash and sign a transaction, broadcast via JSON-RPC. Zcash breaks this pattern in two ways:
8+
9+
1. **Key derivation uses ZIP-32, not BIP-44.** Zcash unified addresses contain Orchard and Sapling receivers, which require the raw BIP-39 seed passed through the ZIP-32 derivation scheme — not a BIP-32 derived private key.
10+
11+
2. **Signing operates on PCZTs, not raw transactions.** Zcash shielded transactions require zero-knowledge proofs and per-pool spend authorization signatures (RedPallas for Orchard, Jubjub for Sapling). OWS handles only the signing step. The ZK proof generation happens externally.
12+
13+
## Wallet creation
14+
15+
```bash
16+
ows wallet create --name my-wallet
17+
```
18+
19+
Zcash appears alongside all other chains. The derived address is a unified address (`u1...`) with Orchard and Sapling receivers — shielded by default.
20+
21+
```
22+
eip155:1 → 0xab16a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb
23+
solana:... → 7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q
24+
zcash:mainnet → u1qxnwj8h...
25+
```
26+
27+
Under the hood, OWS detects that the Zcash signer requires the raw seed (`needs_raw_seed() → true`) and passes the full 64-byte BIP-39 seed through ZIP-32 derivation instead of BIP-44:
28+
29+
```
30+
BIP-39 Mnemonic
31+
32+
33+
Raw Seed (64 bytes)
34+
35+
├── BIP-44 path → EVM, Solana, Bitcoin, ...
36+
37+
└── ZIP-32 → Unified Spending Key
38+
39+
├── Orchard receiver
40+
├── Sapling receiver
41+
└── Unified Address (u1...)
42+
```
43+
44+
## Address derivation
45+
46+
To derive a Zcash address from an existing mnemonic:
47+
48+
```bash
49+
echo "your twelve word mnemonic ..." | ows derive --chain zcash
50+
```
51+
52+
This returns a unified address. To derive all chains at once:
53+
54+
```bash
55+
echo "your twelve word mnemonic ..." | ows derive
56+
```
57+
58+
## Signing a PCZT
59+
60+
### The PCZT pipeline
61+
62+
Zcash transactions are built collaboratively using the PCZT format. The roles are:
63+
64+
```
65+
Creator → Builds the transaction structure (inputs, outputs, amounts)
66+
67+
Prover → Generates zero-knowledge proofs (Sapling, Orchard)
68+
69+
Signer (OWS) → Applies spend authorization signatures
70+
71+
Finalizer → Extracts the signed transaction
72+
73+
Broadcaster → Sends to the network via lightwalletd
74+
```
75+
76+
OWS fills the **Signer** role. It receives a PCZT that already has ZK proofs applied, decrypts the spending key from the vault, signs the relevant inputs, and returns the signed PCZT.
77+
78+
### CLI usage
79+
80+
```bash
81+
# Sign a PCZT (hex-encoded)
82+
ows sign tx --chain zcash --wallet my-wallet --tx <pczt_hex>
83+
```
84+
85+
The `<pczt_hex>` must be a serialized PCZT that has already been through the Creator and Prover stages. Tools that produce PCZTs include:
86+
87+
- **zipher-cli** (`zipher create-pczt`)
88+
- **Zodl** (the Zcash reference wallet, via `zcash_client_backend`)
89+
- Any tool using `zcash_client_backend::data_api::wallet::create_pczt_from_proposal`
90+
91+
The output is the signed PCZT in hex. The caller is responsible for finalization and broadcast.
92+
93+
### Security: trusting the PCZT source
94+
95+
OWS signs whatever PCZT is passed to it — the same trust model as every other chain. When you call `ows sign tx --chain evm`, OWS signs the unsigned EVM transaction without inspecting whether the recipient or amount is what you intended. The caller (your agent, your CLI, your wallet) is responsible for constructing a safe transaction.
96+
97+
For Zcash, this means: if a malicious PCZT is constructed to send your funds to an attacker's address, OWS will sign it. The PCZT format does carry metadata (amounts, recipients, memos) that a higher-level application could inspect before requesting a signature, but OWS itself operates at the signing primitive level — it does not enforce spending policies at the PCZT layer.
98+
99+
This is consistent with the OWS security model: the vault holds keys, the signer signs, and the policy engine (if configured) enforces spending limits. Transaction construction and validation are the caller's responsibility across all chains.
100+
101+
### Sign and broadcast
102+
103+
For end-to-end sending:
104+
105+
```bash
106+
ows sign send-tx --chain zcash --wallet my-wallet --tx <pczt_hex>
107+
```
108+
109+
This signs the PCZT, extracts the finalized transaction, and broadcasts it to lightwalletd via gRPC. The default endpoint is `zec.rocks:443` for mainnet.
110+
111+
Returns the transaction ID (txid) on success.
112+
113+
## What OWS signs
114+
115+
When `sign tx --chain zcash` is called, OWS:
116+
117+
1. Decrypts the BIP-39 mnemonic from the vault
118+
2. Derives the Unified Spending Key via ZIP-32
119+
3. Extracts per-pool signing keys:
120+
- **Orchard:** `SpendAuthorizingKey` (RedPallas)
121+
- **Sapling:** `ask` (Jubjub)
122+
- **Transparent:** secp256k1 secret key
123+
4. Iterates over PCZT inputs and signs those matching the derived keys
124+
5. Skips dummy/padding actions (standard Orchard behavior)
125+
6. Returns the signed PCZT
126+
127+
The spending key is zeroized after use.
128+
129+
## Configuration
130+
131+
### Default RPC endpoints
132+
133+
| Network | Endpoint |
134+
|---------|----------|
135+
| `zcash:mainnet` | `https://zec.rocks:443` |
136+
| `zcash:testnet` | `https://testnet.zec.rocks:443` |
137+
138+
Override via `~/.ows/config.toml`:
139+
140+
```toml
141+
[rpc]
142+
"zcash:mainnet" = "https://your-lightwalletd:443"
143+
```
144+
145+
Or pass `--rpc-url` on the CLI.
146+
147+
### Feature flag
148+
149+
Zcash shielded support requires the `zcash-shielded` feature flag, which is enabled by default in the CLI. The feature adds dependencies on `zcash_keys`, `pczt`, `orchard`, and `sapling-crypto` for ZIP-32 derivation and PCZT signing.
150+
151+
Without `zcash-shielded`, Zcash falls back to transparent-only support (t-addresses, secp256k1 signing).
152+
153+
## Dependencies
154+
155+
| Crate | Purpose |
156+
|-------|---------|
157+
| `zcash_keys` 0.12 | ZIP-32 key derivation, unified address encoding |
158+
| `zcash_protocol` 0.7 | Network parameters (mainnet/testnet) |
159+
| `zip32` 0.2 | Account ID types |
160+
| `pczt` 0.5 | PCZT parsing, Signer role |
161+
| `orchard` 0.11 | Orchard spend authorization key types |
162+
| `sapling-crypto` 0.5 | Sapling spend auth key (`ask`) |
163+
| `zcash_transparent` 0.6 | Transparent key derivation |
164+
| `zcash_primitives` 0.26 | Transaction serialization (for broadcast) |
165+
166+
All crates are from the official [librustzcash](https://github.com/zcash/librustzcash) ecosystem, originally built by Electric Coin Company and now maintained by [ZODL](https://zodl.com) (Zcash Open Development Lab).
167+
168+
## End-to-end example
169+
170+
A complete shielded send using OWS and zipher-cli:
171+
172+
```bash
173+
# 1. Create an OWS wallet
174+
ows wallet create --name agent-wallet
175+
176+
# 2. Fund the Zcash address (send ZEC to the u1... address)
177+
178+
# 3. Build a PCZT with zipher-cli (Creator + Prover)
179+
PCZT=$(zipher create-pczt \
180+
--to u1recipient... \
181+
--amount 0.01 \
182+
--data-dir ~/.zipher)
183+
184+
# 4. Sign with OWS (Signer)
185+
ows sign send-tx --chain zcash --wallet agent-wallet --tx $PCZT
186+
187+
# 5. Transaction is broadcast and visible on a block explorer
188+
```
189+
190+
## References
191+
192+
- [ZIP-32: Shielded Hierarchical Deterministic Wallets](https://zips.z.cash/zip-0032)
193+
- [ZIP-316: Unified Addresses](https://zips.z.cash/zip-0316)
194+
- [ZIP-244: Transaction Identifier and Commitment](https://zips.z.cash/zip-0244)
195+
- [PCZT specification](https://github.com/zcash/zips/pull/766)
196+
- [OWS Signing Interface](02-signing-interface.md)
197+
- [OWS Supported Chains](07-supported-chains.md)

0 commit comments

Comments
 (0)