Metadata-Version: 2.4
Name: wallet-privacy-testkit
Version: 0.2.0
Summary: Adversarial network privacy and transaction-delivery tests for Zcash wallets
Author: Wallet Privacy Testkit contributors
License-Expression: MIT OR Apache-2.0
Project-URL: Homepage, https://github.com/ztsalexey/wallet-privacy-testkit
Project-URL: Repository, https://github.com/ztsalexey/wallet-privacy-testkit
Project-URL: Issues, https://github.com/ztsalexey/wallet-privacy-testkit/issues
Project-URL: Changelog, https://github.com/ztsalexey/wallet-privacy-testkit/blob/main/CHANGELOG.md
Keywords: zcash,wallet,privacy,testing,grpc,tls
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Security
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Requires-Dist: grpcio<2,>=1.70
Requires-Dist: protobuf<8,>=5
Dynamic: license-file

# 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](https://github.com/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.2.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](docs/METHODOLOGY.md) defines what each result means. The [threat model](docs/THREAT_MODEL.md) states what it does not establish.

## Install

Python 3.11 or newer is required.

Install version 0.2.0 from [PyPI](https://pypi.org/project/wallet-privacy-testkit/0.2.0/):

```sh
python3 -m venv .venv
source .venv/bin/activate
python -m pip install wallet-privacy-testkit==0.2.0
wpt --help
```

## Develop from a source checkout

```sh
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](https://github.com/ztsalexey/wallet-privacy-testkit/releases). After activating a virtual environment, install a downloaded wheel with `python -m pip install ./wallet_privacy_testkit-0.2.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`:

```sh
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:

```sh
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

Version 0.2.0 includes `after-hold`, report verification, and continuous-traffic analysis. The [disposable Compose lab](examples/regtest/README.md) is included in the source archive and repository.

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:

```sh
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 |
| `after-hold` | Forward submission and hold its response until the client cancels, reaches its deadline, or the relay stops |

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.

Forwarding respects shorter client deadlines, caps each upstream RPC at 120 seconds, and cancels upstream calls when the downstream RPC ends. It forwards repeated and binary application request metadata, response headers, and trailers, including upstream error details. Transport-owned fields are filtered. Deliberately lost or held responses suppress upstream headers and trailers too. Credentials are forwarded but not recorded in relay events. It remains a controlled test instrument.

## Recovery evidence and continuous traffic

The lab tests lost acknowledgements, a wallet crash, an indexer restart, a measured outage, and recovery through a fresh second indexer. Each report retains actual recipient balances, node confirmations and mempools, wallet states, and submission hashes. A live payment left unmined must fail the recovery assertions. Recompute the assertions yourself:

```sh
wpt verify-recovery /tmp/wpt-first-run/report.json
wpt analyze-privacy /tmp/wpt-first-run/privacy-manifest.json
```

The privacy experiment keeps the wallet running during two continuous sessions, with periodic sync and block production. It fits a TLS-record-size threshold on calibration windows, then reports true positives, false positives, misses, and true negatives on a separate evaluation session. Features use fixed windows without payment labels; command intervals supply ground truth only for calibration and scoring. Metadata traces and their checksums let others recompute the result.

These checks establish consistency of supplied observations, not their authenticity. The small, scheduled local experiment measures send activity under its stated conditions; it does not establish transaction linkage, user identification, or real-world privacy. Historical development summaries lack the new observations and cannot pass the new verifier.

For a larger study with a frozen detector, repeated sessions, emulated latency/bandwidth, and the independent zcash-devtool wallet:

```sh
python3 examples/regtest/run.py --context orbstack --study --output /tmp/wpt-study
wpt analyze-study /tmp/wpt-study/study-manifest.json
```

The study records its plan before capture and saves the fitted detector before any evaluation session. It evaluates another wallet implementation with a different CLI process lifecycle, so it does not rank consumer wallet privacy. See the [study methodology](docs/PRIVACY_STUDY.md) and [retained 15-session results](evidence/privacy-study-020/README.md). All 45 payments confirmed. The frozen cutoff detected Zingolib send activity but missed every send-active window from zcash-devtool, whose largest records fell three bytes below the cutoff. This demonstrates a limit of the detector, not a privacy ranking.

## 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.

```json
[
  {"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](evidence/zingolib-v5-regtest/REPORT.md) 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.

Earlier development's [fresh Compose recovery experiments](evidence/zingolib-v5-compose/README.md) passed on OrbStack ARM64 and native Linux Docker x86-64. They construct new payments and verify recovery after a lost acknowledgement and after killing the wallet before acknowledgement. Both runs confirmed the payments and the exact recipient balance increases. Docker Desktop has not been separately tested.

The [expanded dev1 evidence](evidence/zingolib-v5-continuous/README.md) retains independently checkable reports and full metadata traces from successful OrbStack and native Linux runs. Both passed five recovery scenarios and detected the unmined-payment control. The held-out send-window detector recorded misses on both platforms and a false positive on OrbStack; the report includes every window and explains the small sample’s limits.

## 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](docs/COMPETITIVE_LANDSCAPE.md) and [release checklist](docs/RELEASE_CHECKLIST.md).

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.
