Wallet Privacy Testkit for Zcash
Wallet Privacy Testkit is an adversarial test layer for Zcash wallet-to-indexer traffic. It records the lengths and timing visible outside TLS, injects uncertainty around SendTransaction, and evaluates size-only transaction matching without awarding full credit to ties.
This is an independent project maintained at ztsalexey/wallet-privacy-testkit. It is not affiliated with or endorsed by the Zcash Foundation, Electric Coin Company, or any wallet or indexer maintainer.
Version 0.1.0 is a research preview. It supplies reusable test components and a verified Zingolib case study. It does not certify a wallet, assign a privacy score, inspect mainnet funds, or replace a wallet's existing integration tests.
Why this exists
A shielded transaction can hide its participants and value on-chain while its network behavior still leaks information. A second failure mode appears when a node accepts a payment but the wallet loses the response. The wallet must distinguish rejection from an unknown outcome and recover without creating another payment.
Existing projects already provision Zcash regtest networks and test wallet behavior. This testkit attaches to those environments and contributes two narrower capabilities:
- passive ciphertext metadata capture, without TLS keys or payload logging;
- protocol-aware failure injection before or after transaction submission.
The methodology defines what each result means. The threat model states what it does not establish.
Install from a source checkout
Python 3.11 or newer is required.
python3 -m venv .venv
source .venv/bin/activate
.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/python -m pip install --no-deps -e .
.venv/bin/python -m unittest discover -s tests -v
The release was developed with Python 3.12.9, gRPC 1.83.1, and protobuf 7.36.1. CI tests the pinned development environment on Python 3.11–3.13 on Linux and macOS. The broader runtime dependency ranges are not exhaustively tested.
Packaged wheels and source archives are available from GitHub Releases. After activating a virtual environment, install a downloaded wheel with python -m pip install ./wallet_privacy_testkit-0.1.0-py3-none-any.whl.
Passive TLS metadata capture
Point a wallet at the printed local port while the target indexer remains at localhost:9067:
wpt capture \
--target-host localhost \
--target-port 9067 \
--listen-port 7443 \
--output trace.jsonl
Stop the capture with Ctrl-C, then validate that every captured TCP byte belongs to a complete TLS record:
wpt summarize trace.jsonl
The forwarder copies bytes unchanged. Its trace contains connection numbers, monotonic timestamps, directions, byte counts, TLS content types, and TLS record lengths. It does not terminate TLS, store addresses, or record packet contents. A process boundary supplied by the test operator is still side information and must be disclosed in a report.
Transaction delivery faults
The semantic relay terminates a test TLS connection and forwards the real gRPC service. It understands only enough of the lightwalletd protocol to identify and hash RawTransaction.data; it never writes signed transaction bytes. All other known RPCs are forwarded as opaque bytes using their required streaming cardinality.
The forwarding table was checked against canonical lightwallet-protocol v0.5.0. A later protocol version that adds a streaming RPC requires a testkit update before that RPC can pass through correctly.
For a TLS upstream and TLS-facing wallet:
wpt fault-relay \
--upstream localhost:9067 \
--upstream-ca test-ca.pem \
--certificate localhost.pem \
--private-key localhost.key \
--mode after-once \
--events events.json
The supported modes are:
| Mode | Behavior |
|---|---|
before-once |
Fail the first submission without forwarding it, then forward normally |
after-once |
Forward the first submission and discard its response, then forward normally |
after-all |
Forward every submission and discard every response |
Use the relay only with disposable wallets and isolated regtest funds. The tool deliberately changes transaction-delivery behavior. The test operator remains responsible for checking the node's mempool or chain and the wallet's eventual state.
An insecure local upstream or client can omit the certificate options. The relay binds to 127.0.0.1 by default. Supplying only one of --certificate and --private-key is rejected.
The printed endpoint uses 127.0.0.1. When the wallet-facing side uses TLS, the server certificate must therefore contain the IP address 127.0.0.1 in its Subject Alternative Name. The upstream CA file is required for a TLS upstream; omitting it selects an insecure upstream channel.
Size-only matching
wpt match samples.json accepts a JSON array. Each row must contain a unique id, a batch, observed_bytes, and the corresponding public transaction_bytes. Batch 0 calibrates a single median byte offset by default. Every other observation is compared only with candidates in its own batch.
[
{"id":"train-a","batch":0,"observed_bytes":9207,"transaction_bytes":9165},
{"id":"train-b","batch":0,"observed_bytes":12363,"transaction_bytes":12321},
{"id":"test-a","batch":1,"observed_bytes":9207,"transaction_bytes":9165},
{"id":"test-b","batch":1,"observed_bytes":12363,"transaction_bytes":12321}
]
Nearest candidates share credit when their distance ties. A batch with fewer than two candidates is rejected because singleton identification says nothing about the matcher. The tool reports a random reference computed from each held-out batch's candidate count.
Verified case study
The included Zingolib v5 regtest report records 18 newly constructed shielded payments and three delivery-fault transactions in the complete run. The wallet reported a payment as failed after its acknowledgement was lost even though Zebra had accepted it. Mining and synchronization corrected the status to confirmed. Current Zingolib development code already includes related duplicate and delivery-check handling, so this is regression evidence rather than a claim of an unresolved current bug.
The case study also found that encrypted lengths distinguished 1-, 2-, and 4-recipient transaction structures in a favorable observer model. Equal-structure payments tied at chance. The result does not estimate deanonymization in normal wallet traffic.
Project status
The release boundary is intentionally small. Z3, Zcash Integration Tests, Regchest, and wallet-specific suites already cover network provisioning and broad functional behavior. The testkit is designed to complement them. See the competitive and overlap review and release checklist.
Upstream contributions should begin with a maintainer-acknowledged issue and a narrowly scoped regression, following the target repository's contribution policy.
License
Licensed under either the MIT License or Apache License 2.0, at your option.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wallet_privacy_testkit-0.1.0.tar.gz.
File metadata
- Download URL: wallet_privacy_testkit-0.1.0.tar.gz
- Upload date:
- Size: 38.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e79c0794548188c83f074a240137165f2e565ba32488df02060b111f91166ee6
|
|
| MD5 |
52980e336287e23549f13bf12661aacc
|
|
| BLAKE2b-256 |
46a42dda87423ece5620a71eb2fd90d2be9d9ab2193da715fd833009373e3fc0
|
Provenance
The following attestation bundles were made for wallet_privacy_testkit-0.1.0.tar.gz:
Publisher:
publish.yml on ztsalexey/wallet-privacy-testkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wallet_privacy_testkit-0.1.0.tar.gz -
Subject digest:
e79c0794548188c83f074a240137165f2e565ba32488df02060b111f91166ee6 - Sigstore transparency entry: 2730380976
- Sigstore integration time:
-
Permalink:
ztsalexey/wallet-privacy-testkit@9adf3845971585097eb907062036c819dd7d8b21 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ztsalexey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9adf3845971585097eb907062036c819dd7d8b21 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file wallet_privacy_testkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wallet_privacy_testkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed684e3a9736c3a57859cc32b2600d3126efb99babe0e9b10d2c7263e04a0a14
|
|
| MD5 |
6acefb6baa6146fad49baaad0393f0b2
|
|
| BLAKE2b-256 |
f0981c7fcd75c78f76f0bc3ad580ec97180a41a285f9548c4a29a678c02cc16a
|
Provenance
The following attestation bundles were made for wallet_privacy_testkit-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ztsalexey/wallet-privacy-testkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wallet_privacy_testkit-0.1.0-py3-none-any.whl -
Subject digest:
ed684e3a9736c3a57859cc32b2600d3126efb99babe0e9b10d2c7263e04a0a14 - Sigstore transparency entry: 2730381251
- Sigstore integration time:
-
Permalink:
ztsalexey/wallet-privacy-testkit@9adf3845971585097eb907062036c819dd7d8b21 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ztsalexey
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9adf3845971585097eb907062036c819dd7d8b21 -
Trigger Event:
workflow_dispatch
-
Statement type: