validpay 0.1.0__tar.gz → 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- validpay-1.0.1/CHANGELOG.md +46 -0
- {validpay-0.1.0 → validpay-1.0.1}/LICENSE +1 -1
- validpay-1.0.1/MANIFEST.in +7 -0
- validpay-1.0.1/PKG-INFO +260 -0
- validpay-1.0.1/README.md +204 -0
- validpay-1.0.1/pyproject.toml +58 -0
- validpay-1.0.1/setup.cfg +4 -0
- validpay-1.0.1/validpay/__init__.py +52 -0
- validpay-1.0.1/validpay/_timelock.py +53 -0
- validpay-1.0.1/validpay/binding.py +154 -0
- validpay-1.0.1/validpay/client.py +967 -0
- validpay-1.0.1/validpay/crypto.py +219 -0
- validpay-1.0.1/validpay/errors.py +32 -0
- validpay-1.0.1/validpay/offline.py +312 -0
- validpay-1.0.1/validpay/py.typed +0 -0
- validpay-1.0.1/validpay/types.py +56 -0
- validpay-1.0.1/validpay.egg-info/PKG-INFO +260 -0
- validpay-1.0.1/validpay.egg-info/SOURCES.txt +19 -0
- validpay-1.0.1/validpay.egg-info/dependency_links.txt +1 -0
- validpay-1.0.1/validpay.egg-info/requires.txt +10 -0
- validpay-1.0.1/validpay.egg-info/top_level.txt +1 -0
- validpay-0.1.0/PKG-INFO +0 -76
- validpay-0.1.0/README.md +0 -49
- validpay-0.1.0/pyproject.toml +0 -37
- validpay-0.1.0/src/validpay/__init__.py +0 -15
- validpay-0.1.0/src/validpay/client.py +0 -95
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the ValidPay Python SDK will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.1] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- `DEFAULT_BASE_URL` is now `https://api.validpay.com` (Prompt 086B —
|
|
13
|
+
primary domain migrated from validpay.io). The legacy host keeps
|
|
14
|
+
working via Cloudflare 301 redirects, so 1.0.0 installs are
|
|
15
|
+
unaffected; new installs default to `.com`. The `base_url` constructor
|
|
16
|
+
argument continues to override.
|
|
17
|
+
- README + `pyproject.toml` URLs (Homepage, Documentation) now point at
|
|
18
|
+
`validpay.com`.
|
|
19
|
+
|
|
20
|
+
## [1.0.0] - 2026-05-03
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Core client** (`ValidPayClient`) — create, verify, revoke, and reinstate
|
|
25
|
+
document intents via the ValidPay API.
|
|
26
|
+
- **AES-256-GCM encryption** — client-side encryption/decryption with
|
|
27
|
+
commitment hash verification (Patent B).
|
|
28
|
+
- **Split-key verification** (Patent C) — XOR key splitting into Share A
|
|
29
|
+
(document) and Share B (server). Neither alone decrypts.
|
|
30
|
+
- **Time-locked verification** (Patent D) — optional `valid_from` /
|
|
31
|
+
`valid_until` windows with client-side enforcement.
|
|
32
|
+
- **Selective field disclosure** (Patent E) — per-field encryption with
|
|
33
|
+
role-based disclosure policies.
|
|
34
|
+
- **Physical medium binding** (Patent F) — perceptual hashing and binding
|
|
35
|
+
zone comparison for document-to-physical matching.
|
|
36
|
+
Requires optional `binding` extra (`pip install validpay[binding]`).
|
|
37
|
+
- **Chain-of-custody tracking** (Patent G) — verification event audit log
|
|
38
|
+
via the API.
|
|
39
|
+
- **Blind revocation** (Patent H) — revoke/reinstate intents without
|
|
40
|
+
decrypting the payload.
|
|
41
|
+
- **Offline verification** (`OfflineCache`) — encrypted local cache for
|
|
42
|
+
offline verification with staleness tracking and revocation sync.
|
|
43
|
+
- **Batch intent creation** — create up to 100 intents in a single API call.
|
|
44
|
+
- **115 automated tests** across 4 test modules.
|
|
45
|
+
|
|
46
|
+
[1.0.0]: https://github.com/ValidPay-io/validpay-python-sdk/releases/tag/v1.0.0
|
validpay-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: validpay
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Official ValidPay Python SDK — client-side AES-256-GCM encryption + ValidPay API client
|
|
5
|
+
Author-email: ValidPay <dev@validpay.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 ValidPay
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://validpay.com
|
|
29
|
+
Project-URL: Documentation, https://validpay.com/docs/api
|
|
30
|
+
Project-URL: Repository, https://github.com/ValidPay-io/validpay-python-sdk
|
|
31
|
+
Project-URL: Issues, https://github.com/ValidPay-io/validpay-python-sdk/issues
|
|
32
|
+
Project-URL: Changelog, https://github.com/ValidPay-io/validpay-python-sdk/blob/main/CHANGELOG.md
|
|
33
|
+
Keywords: validpay,encryption,aes-256-gcm,document-verification,blind-escrow
|
|
34
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Topic :: Security :: Cryptography
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: cryptography>=41.0
|
|
48
|
+
Requires-Dist: requests>=2.28
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
51
|
+
Provides-Extra: binding
|
|
52
|
+
Requires-Dist: Pillow>=10.0; extra == "binding"
|
|
53
|
+
Requires-Dist: numpy>=1.24; extra == "binding"
|
|
54
|
+
Requires-Dist: scipy>=1.10; extra == "binding"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# ValidPay Python SDK
|
|
58
|
+
|
|
59
|
+
Official Python SDK for the [ValidPay](https://validpay.com) document
|
|
60
|
+
verification API. Provides client-side AES-256-GCM encryption and a thin
|
|
61
|
+
client around the ValidPay HTTP API.
|
|
62
|
+
|
|
63
|
+
The encryption format is wire-compatible with the
|
|
64
|
+
[Node.js SDK](https://github.com/ValidPay-io/validpay-node-sdk): a payload
|
|
65
|
+
encrypted by the Python SDK can be decrypted by the Node SDK and vice
|
|
66
|
+
versa.
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install validpay
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For physical-binding support (Patent F — image-based binding zones), install
|
|
75
|
+
the optional extras:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install validpay[binding]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Requires Python 3.9+.
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from validpay import ValidPayClient
|
|
87
|
+
|
|
88
|
+
client = ValidPayClient(api_key="vp_live_xxx")
|
|
89
|
+
|
|
90
|
+
# Create a single intent — the payload is encrypted locally before
|
|
91
|
+
# anything leaves your process. Only the ciphertext is sent to ValidPay.
|
|
92
|
+
result = client.create_intent(
|
|
93
|
+
document_type="check",
|
|
94
|
+
payload={"payee": "John Doe", "amount": 1500.00, "check_number": "10042"},
|
|
95
|
+
)
|
|
96
|
+
print(result.retrieval_id) # vp_abc123def456
|
|
97
|
+
print(result.key) # base64 AES-256 key — deliver out-of-band
|
|
98
|
+
|
|
99
|
+
# Create up to 100 intents in one round trip.
|
|
100
|
+
results = client.create_intent_batch([
|
|
101
|
+
{"document_type": "check", "payload": {"payee": "Alice", "amount": 500}},
|
|
102
|
+
{"document_type": "check", "payload": {"payee": "Bob", "amount": 750}},
|
|
103
|
+
])
|
|
104
|
+
for r in results:
|
|
105
|
+
print(r.retrieval_id, r.key)
|
|
106
|
+
|
|
107
|
+
# Verify (retrieve + decrypt). No API key required for this endpoint.
|
|
108
|
+
verification = client.verify_intent(
|
|
109
|
+
retrieval_id="vp_abc123def456",
|
|
110
|
+
key=result.key,
|
|
111
|
+
)
|
|
112
|
+
print(verification.payload) # decrypted dict
|
|
113
|
+
print(verification.issuer) # "Acme Corp"
|
|
114
|
+
print(verification.issuer_verified) # True
|
|
115
|
+
print(verification.status) # "active"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Time-Locked Verification (Patent D)
|
|
119
|
+
|
|
120
|
+
Restrict when a document can be verified by specifying a validity window:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from datetime import datetime, timezone, timedelta
|
|
124
|
+
|
|
125
|
+
result = client.create_intent(
|
|
126
|
+
document_type="check",
|
|
127
|
+
payload={"payee": "Jane Doe", "amount": 1500.00},
|
|
128
|
+
valid_from=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
|
|
129
|
+
valid_until=(datetime.now(timezone.utc) + timedelta(days=30)).isoformat(),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Later, when verifying:
|
|
133
|
+
verified = client.verify_intent(result.retrieval_id, result.key)
|
|
134
|
+
print(verified.time_lock_status) # "valid", "not_yet_valid", or "expired"
|
|
135
|
+
print(verified.valid_from) # ISO-8601 timestamp or None
|
|
136
|
+
print(verified.valid_until) # ISO-8601 timestamp or None
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Time-lock status is informational — the SDK always returns the decrypted
|
|
140
|
+
payload regardless of the time window. Your application decides how to
|
|
141
|
+
handle `not_yet_valid` or `expired` results. The server stores the
|
|
142
|
+
timestamps but never enforces them; this preserves the blind intermediary
|
|
143
|
+
model (the server never decides whether a document is "still good").
|
|
144
|
+
|
|
145
|
+
The same `valid_from` / `valid_until` keyword arguments are accepted by
|
|
146
|
+
`create_intent_batch` (per-item), `create_split_key_intent`, and
|
|
147
|
+
`create_selective_intent`.
|
|
148
|
+
|
|
149
|
+
### Split-key intents (Patent C)
|
|
150
|
+
|
|
151
|
+
Splits the AES key into two XOR shares: Share A is returned to the caller
|
|
152
|
+
(typically embedded in the QR code), Share B is stored server-side. Neither
|
|
153
|
+
share alone can decrypt the payload.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
result = client.create_split_key_intent(
|
|
157
|
+
document_type="ssn_card",
|
|
158
|
+
payload={"ssn": "123-45-6789"},
|
|
159
|
+
)
|
|
160
|
+
# result.key is Share A — pair it with Share B at verification time.
|
|
161
|
+
|
|
162
|
+
verified = client.verify_split_key_intent(result.retrieval_id, result.key)
|
|
163
|
+
print(verified.payload)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Selective disclosure (Patent E)
|
|
167
|
+
|
|
168
|
+
Each field is encrypted with its own per-field key. A disclosure policy maps
|
|
169
|
+
role names to the fields that role may decrypt. A `full` role with access to
|
|
170
|
+
every field is added automatically.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
result = client.create_selective_intent(
|
|
174
|
+
document_type="check",
|
|
175
|
+
payload={"payee": "Alice", "amount": 1500.00, "memo": "rent"},
|
|
176
|
+
disclosure_policy={"bank": ["amount"], "auditor": ["amount", "payee"]},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Bank sees only 'amount'; other fields come back as REDACTED markers.
|
|
180
|
+
verified = client.verify_selective_intent(result.retrieval_id, result.key, role="bank")
|
|
181
|
+
print(verified.payload)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`create_selective_intent` accepts `split_key=True` to combine Patents C + E.
|
|
185
|
+
|
|
186
|
+
### Revocation (Patent H — Blind Revocation)
|
|
187
|
+
|
|
188
|
+
Issuers can revoke or reinstate an intent without decrypting it. Verifiers
|
|
189
|
+
of a revoked intent receive `status="revoked"` and no encrypted payload.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
client.revoke_intent("vp_abc123def456", reason="Stop payment")
|
|
193
|
+
client.reinstate_intent("vp_abc123def456", reason="False alarm")
|
|
194
|
+
|
|
195
|
+
history = client.get_revocation_history("vp_abc123def456")
|
|
196
|
+
for event in history:
|
|
197
|
+
print(event["action"], event["reason"], event["performed_at"])
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## API
|
|
201
|
+
|
|
202
|
+
### `ValidPayClient(api_key, *, base_url=..., timeout=30.0, session=None)`
|
|
203
|
+
|
|
204
|
+
- `api_key` — your ValidPay API key (required for create endpoints).
|
|
205
|
+
- `base_url` — defaults to `https://api.validpay.com`.
|
|
206
|
+
- `timeout` — per-request timeout in seconds.
|
|
207
|
+
- `session` — optionally provide a `requests.Session` for connection
|
|
208
|
+
pooling, custom adapters, or mocking in tests.
|
|
209
|
+
|
|
210
|
+
### `client.create_intent(document_type, payload) -> CreateIntentResult`
|
|
211
|
+
|
|
212
|
+
Encrypts `payload` (any JSON-serializable value) under a freshly
|
|
213
|
+
generated AES-256 key and registers it with ValidPay. Returns the
|
|
214
|
+
retrieval id and the key. **The key is never sent to ValidPay** — you
|
|
215
|
+
must hand it off out-of-band to whoever needs to verify the intent.
|
|
216
|
+
|
|
217
|
+
### `client.create_intent_batch(intents) -> list[CreateIntentResult]`
|
|
218
|
+
|
|
219
|
+
Bulk version. `intents` is an iterable of mappings shaped
|
|
220
|
+
`{"document_type": str, "payload": Any}`, with 1–100 items. Each intent
|
|
221
|
+
gets its own unique key. Result order matches input order.
|
|
222
|
+
|
|
223
|
+
### `client.verify_intent(retrieval_id, key) -> VerifyIntentResult`
|
|
224
|
+
|
|
225
|
+
Fetches the intent (public endpoint, no API key required), decrypts the
|
|
226
|
+
payload locally, and returns issuer metadata + the decrypted payload.
|
|
227
|
+
|
|
228
|
+
### Errors
|
|
229
|
+
|
|
230
|
+
All SDK and API errors are raised as `ValidPayError`, which exposes:
|
|
231
|
+
|
|
232
|
+
- `code` — machine-readable code (e.g. `"unauthorized"`, `"not_found"`,
|
|
233
|
+
`"decryption_failed"`, `"invalid_key"`, `"network_error"`).
|
|
234
|
+
- `status` — HTTP status when the error came from the API.
|
|
235
|
+
- `details` — raw error body / extra context when available.
|
|
236
|
+
|
|
237
|
+
### Low-level crypto
|
|
238
|
+
|
|
239
|
+
For advanced use cases the encryption primitives are exported directly:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from validpay import generate_key, encrypt, decrypt
|
|
243
|
+
|
|
244
|
+
key = generate_key()
|
|
245
|
+
blob = encrypt('{"hello": "world"}', key)
|
|
246
|
+
assert decrypt(blob, key) == '{"hello": "world"}'
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Wire format: `base64(iv[12] || authTag[16] || ciphertext)`.
|
|
250
|
+
|
|
251
|
+
## Development
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
pip install -e ".[dev]"
|
|
255
|
+
pytest
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT — see `LICENSE`.
|
validpay-1.0.1/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# ValidPay Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [ValidPay](https://validpay.com) document
|
|
4
|
+
verification API. Provides client-side AES-256-GCM encryption and a thin
|
|
5
|
+
client around the ValidPay HTTP API.
|
|
6
|
+
|
|
7
|
+
The encryption format is wire-compatible with the
|
|
8
|
+
[Node.js SDK](https://github.com/ValidPay-io/validpay-node-sdk): a payload
|
|
9
|
+
encrypted by the Python SDK can be decrypted by the Node SDK and vice
|
|
10
|
+
versa.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install validpay
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For physical-binding support (Patent F — image-based binding zones), install
|
|
19
|
+
the optional extras:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install validpay[binding]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requires Python 3.9+.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from validpay import ValidPayClient
|
|
31
|
+
|
|
32
|
+
client = ValidPayClient(api_key="vp_live_xxx")
|
|
33
|
+
|
|
34
|
+
# Create a single intent — the payload is encrypted locally before
|
|
35
|
+
# anything leaves your process. Only the ciphertext is sent to ValidPay.
|
|
36
|
+
result = client.create_intent(
|
|
37
|
+
document_type="check",
|
|
38
|
+
payload={"payee": "John Doe", "amount": 1500.00, "check_number": "10042"},
|
|
39
|
+
)
|
|
40
|
+
print(result.retrieval_id) # vp_abc123def456
|
|
41
|
+
print(result.key) # base64 AES-256 key — deliver out-of-band
|
|
42
|
+
|
|
43
|
+
# Create up to 100 intents in one round trip.
|
|
44
|
+
results = client.create_intent_batch([
|
|
45
|
+
{"document_type": "check", "payload": {"payee": "Alice", "amount": 500}},
|
|
46
|
+
{"document_type": "check", "payload": {"payee": "Bob", "amount": 750}},
|
|
47
|
+
])
|
|
48
|
+
for r in results:
|
|
49
|
+
print(r.retrieval_id, r.key)
|
|
50
|
+
|
|
51
|
+
# Verify (retrieve + decrypt). No API key required for this endpoint.
|
|
52
|
+
verification = client.verify_intent(
|
|
53
|
+
retrieval_id="vp_abc123def456",
|
|
54
|
+
key=result.key,
|
|
55
|
+
)
|
|
56
|
+
print(verification.payload) # decrypted dict
|
|
57
|
+
print(verification.issuer) # "Acme Corp"
|
|
58
|
+
print(verification.issuer_verified) # True
|
|
59
|
+
print(verification.status) # "active"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Time-Locked Verification (Patent D)
|
|
63
|
+
|
|
64
|
+
Restrict when a document can be verified by specifying a validity window:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from datetime import datetime, timezone, timedelta
|
|
68
|
+
|
|
69
|
+
result = client.create_intent(
|
|
70
|
+
document_type="check",
|
|
71
|
+
payload={"payee": "Jane Doe", "amount": 1500.00},
|
|
72
|
+
valid_from=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
|
|
73
|
+
valid_until=(datetime.now(timezone.utc) + timedelta(days=30)).isoformat(),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Later, when verifying:
|
|
77
|
+
verified = client.verify_intent(result.retrieval_id, result.key)
|
|
78
|
+
print(verified.time_lock_status) # "valid", "not_yet_valid", or "expired"
|
|
79
|
+
print(verified.valid_from) # ISO-8601 timestamp or None
|
|
80
|
+
print(verified.valid_until) # ISO-8601 timestamp or None
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Time-lock status is informational — the SDK always returns the decrypted
|
|
84
|
+
payload regardless of the time window. Your application decides how to
|
|
85
|
+
handle `not_yet_valid` or `expired` results. The server stores the
|
|
86
|
+
timestamps but never enforces them; this preserves the blind intermediary
|
|
87
|
+
model (the server never decides whether a document is "still good").
|
|
88
|
+
|
|
89
|
+
The same `valid_from` / `valid_until` keyword arguments are accepted by
|
|
90
|
+
`create_intent_batch` (per-item), `create_split_key_intent`, and
|
|
91
|
+
`create_selective_intent`.
|
|
92
|
+
|
|
93
|
+
### Split-key intents (Patent C)
|
|
94
|
+
|
|
95
|
+
Splits the AES key into two XOR shares: Share A is returned to the caller
|
|
96
|
+
(typically embedded in the QR code), Share B is stored server-side. Neither
|
|
97
|
+
share alone can decrypt the payload.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
result = client.create_split_key_intent(
|
|
101
|
+
document_type="ssn_card",
|
|
102
|
+
payload={"ssn": "123-45-6789"},
|
|
103
|
+
)
|
|
104
|
+
# result.key is Share A — pair it with Share B at verification time.
|
|
105
|
+
|
|
106
|
+
verified = client.verify_split_key_intent(result.retrieval_id, result.key)
|
|
107
|
+
print(verified.payload)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Selective disclosure (Patent E)
|
|
111
|
+
|
|
112
|
+
Each field is encrypted with its own per-field key. A disclosure policy maps
|
|
113
|
+
role names to the fields that role may decrypt. A `full` role with access to
|
|
114
|
+
every field is added automatically.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
result = client.create_selective_intent(
|
|
118
|
+
document_type="check",
|
|
119
|
+
payload={"payee": "Alice", "amount": 1500.00, "memo": "rent"},
|
|
120
|
+
disclosure_policy={"bank": ["amount"], "auditor": ["amount", "payee"]},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Bank sees only 'amount'; other fields come back as REDACTED markers.
|
|
124
|
+
verified = client.verify_selective_intent(result.retrieval_id, result.key, role="bank")
|
|
125
|
+
print(verified.payload)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`create_selective_intent` accepts `split_key=True` to combine Patents C + E.
|
|
129
|
+
|
|
130
|
+
### Revocation (Patent H — Blind Revocation)
|
|
131
|
+
|
|
132
|
+
Issuers can revoke or reinstate an intent without decrypting it. Verifiers
|
|
133
|
+
of a revoked intent receive `status="revoked"` and no encrypted payload.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
client.revoke_intent("vp_abc123def456", reason="Stop payment")
|
|
137
|
+
client.reinstate_intent("vp_abc123def456", reason="False alarm")
|
|
138
|
+
|
|
139
|
+
history = client.get_revocation_history("vp_abc123def456")
|
|
140
|
+
for event in history:
|
|
141
|
+
print(event["action"], event["reason"], event["performed_at"])
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## API
|
|
145
|
+
|
|
146
|
+
### `ValidPayClient(api_key, *, base_url=..., timeout=30.0, session=None)`
|
|
147
|
+
|
|
148
|
+
- `api_key` — your ValidPay API key (required for create endpoints).
|
|
149
|
+
- `base_url` — defaults to `https://api.validpay.com`.
|
|
150
|
+
- `timeout` — per-request timeout in seconds.
|
|
151
|
+
- `session` — optionally provide a `requests.Session` for connection
|
|
152
|
+
pooling, custom adapters, or mocking in tests.
|
|
153
|
+
|
|
154
|
+
### `client.create_intent(document_type, payload) -> CreateIntentResult`
|
|
155
|
+
|
|
156
|
+
Encrypts `payload` (any JSON-serializable value) under a freshly
|
|
157
|
+
generated AES-256 key and registers it with ValidPay. Returns the
|
|
158
|
+
retrieval id and the key. **The key is never sent to ValidPay** — you
|
|
159
|
+
must hand it off out-of-band to whoever needs to verify the intent.
|
|
160
|
+
|
|
161
|
+
### `client.create_intent_batch(intents) -> list[CreateIntentResult]`
|
|
162
|
+
|
|
163
|
+
Bulk version. `intents` is an iterable of mappings shaped
|
|
164
|
+
`{"document_type": str, "payload": Any}`, with 1–100 items. Each intent
|
|
165
|
+
gets its own unique key. Result order matches input order.
|
|
166
|
+
|
|
167
|
+
### `client.verify_intent(retrieval_id, key) -> VerifyIntentResult`
|
|
168
|
+
|
|
169
|
+
Fetches the intent (public endpoint, no API key required), decrypts the
|
|
170
|
+
payload locally, and returns issuer metadata + the decrypted payload.
|
|
171
|
+
|
|
172
|
+
### Errors
|
|
173
|
+
|
|
174
|
+
All SDK and API errors are raised as `ValidPayError`, which exposes:
|
|
175
|
+
|
|
176
|
+
- `code` — machine-readable code (e.g. `"unauthorized"`, `"not_found"`,
|
|
177
|
+
`"decryption_failed"`, `"invalid_key"`, `"network_error"`).
|
|
178
|
+
- `status` — HTTP status when the error came from the API.
|
|
179
|
+
- `details` — raw error body / extra context when available.
|
|
180
|
+
|
|
181
|
+
### Low-level crypto
|
|
182
|
+
|
|
183
|
+
For advanced use cases the encryption primitives are exported directly:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from validpay import generate_key, encrypt, decrypt
|
|
187
|
+
|
|
188
|
+
key = generate_key()
|
|
189
|
+
blob = encrypt('{"hello": "world"}', key)
|
|
190
|
+
assert decrypt(blob, key) == '{"hello": "world"}'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Wire format: `base64(iv[12] || authTag[16] || ciphertext)`.
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pip install -e ".[dev]"
|
|
199
|
+
pytest
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "validpay"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "Official ValidPay Python SDK — client-side AES-256-GCM encryption + ValidPay API client"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "ValidPay", email = "dev@validpay.com" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = [
|
|
14
|
+
"validpay",
|
|
15
|
+
"encryption",
|
|
16
|
+
"aes-256-gcm",
|
|
17
|
+
"document-verification",
|
|
18
|
+
"blind-escrow",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 5 - Production/Stable",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Security :: Cryptography",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"cryptography>=41.0",
|
|
34
|
+
"requests>=2.28",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=7.0",
|
|
40
|
+
]
|
|
41
|
+
binding = ["Pillow>=10.0", "numpy>=1.24", "scipy>=1.10"]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://validpay.com"
|
|
45
|
+
Documentation = "https://validpay.com/docs/api"
|
|
46
|
+
Repository = "https://github.com/ValidPay-io/validpay-python-sdk"
|
|
47
|
+
Issues = "https://github.com/ValidPay-io/validpay-python-sdk/issues"
|
|
48
|
+
Changelog = "https://github.com/ValidPay-io/validpay-python-sdk/blob/main/CHANGELOG.md"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
include = ["validpay*"]
|
|
52
|
+
exclude = ["tests*", "check21*"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
validpay = ["py.typed"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
validpay-1.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""ValidPay Python SDK.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
ValidPayClient — thin client for the ValidPay HTTP API
|
|
5
|
+
ValidPayError — exception type raised for all SDK / API errors
|
|
6
|
+
CreateIntentResult, VerifyIntentResult — result dataclasses
|
|
7
|
+
generate_key, encrypt, decrypt — low-level crypto helpers
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .binding import (
|
|
11
|
+
BindingComparisonResult,
|
|
12
|
+
compare_binding_hashes,
|
|
13
|
+
compute_binding_hash,
|
|
14
|
+
)
|
|
15
|
+
from .client import ValidPayClient
|
|
16
|
+
from .crypto import (
|
|
17
|
+
build_key_map,
|
|
18
|
+
combine_key_shares,
|
|
19
|
+
compute_commitment_hash,
|
|
20
|
+
decrypt,
|
|
21
|
+
decrypt_fields,
|
|
22
|
+
encrypt,
|
|
23
|
+
encrypt_fields,
|
|
24
|
+
generate_key,
|
|
25
|
+
split_key,
|
|
26
|
+
)
|
|
27
|
+
from .errors import ValidPayError
|
|
28
|
+
from .offline import OfflineCache, OfflineVerifyResult
|
|
29
|
+
from .types import CreateIntentResult, VerifyIntentResult
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"ValidPayClient",
|
|
33
|
+
"ValidPayError",
|
|
34
|
+
"CreateIntentResult",
|
|
35
|
+
"VerifyIntentResult",
|
|
36
|
+
"generate_key",
|
|
37
|
+
"encrypt",
|
|
38
|
+
"decrypt",
|
|
39
|
+
"compute_commitment_hash",
|
|
40
|
+
"split_key",
|
|
41
|
+
"combine_key_shares",
|
|
42
|
+
"encrypt_fields",
|
|
43
|
+
"build_key_map",
|
|
44
|
+
"decrypt_fields",
|
|
45
|
+
"compute_binding_hash",
|
|
46
|
+
"compare_binding_hashes",
|
|
47
|
+
"BindingComparisonResult",
|
|
48
|
+
"OfflineCache",
|
|
49
|
+
"OfflineVerifyResult",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
__version__ = "1.0.1"
|