ratify-protocol 1.0.0a5__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.
- ratify_protocol-1.0.0a5/PKG-INFO +228 -0
- ratify_protocol-1.0.0a5/README.md +202 -0
- ratify_protocol-1.0.0a5/pyproject.toml +45 -0
- ratify_protocol-1.0.0a5/setup.cfg +4 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/__init__.py +209 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/canonical.py +156 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/constraints.py +205 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/crypto.py +592 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/scope.py +269 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/types.py +408 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol/verify.py +497 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol.egg-info/PKG-INFO +228 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol.egg-info/SOURCES.txt +15 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol.egg-info/dependency_links.txt +1 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol.egg-info/requires.txt +6 -0
- ratify_protocol-1.0.0a5/src/ratify_protocol.egg-info/top_level.txt +1 -0
- ratify_protocol-1.0.0a5/tests/test_conformance.py +439 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ratify-protocol
|
|
3
|
+
Version: 1.0.0a5
|
|
4
|
+
Summary: Cryptographic trust protocol for human-agent and agent-agent interactions — Python reference SDK.
|
|
5
|
+
Author: Identities AI
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/identities-ai/ratify-protocol
|
|
8
|
+
Project-URL: Repository, https://github.com/identities-ai/ratify-protocol
|
|
9
|
+
Project-URL: Issues, https://github.com/identities-ai/ratify-protocol/issues
|
|
10
|
+
Keywords: ratify,delegation,agent,ed25519,ml-dsa,post-quantum,identity,ai-agents
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security :: Cryptography
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: cryptography>=44.0
|
|
22
|
+
Requires-Dist: pqcrypto>=0.3.4
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# ratify-protocol
|
|
28
|
+
|
|
29
|
+
**Python reference SDK for the Ratify Protocol v1 — a cryptographic trust protocol for human-agent and agent-agent interactions as agents start to transact.**
|
|
30
|
+
|
|
31
|
+
Quantum-safe by design: every signature is hybrid Ed25519 + ML-DSA-65 (NIST FIPS 204). Both must verify.
|
|
32
|
+
|
|
33
|
+
Byte-identical interoperability with the Go, TypeScript, and Rust reference implementations. Validated against the **59 canonical test vectors** on every CI run.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install ratify-protocol
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This pulls in two binary dependencies: `cryptography` (Ed25519 via OpenSSL) and `pqcrypto>=0.3.4` (ML-DSA-65). Both ship wheels for Linux / macOS / Windows on CPython 3.10+.
|
|
42
|
+
|
|
43
|
+
### Running the conformance suite from a clean checkout
|
|
44
|
+
|
|
45
|
+
If you cloned the repo and want to run `python -m pytest` against the committed fixtures, the package is not on your path until you install it. Do this:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd sdks/python
|
|
49
|
+
python -m venv .venv && source .venv/bin/activate
|
|
50
|
+
pip install -e '.[dev]' # installs ratify-protocol + cryptography + pqcrypto + pytest
|
|
51
|
+
python -m pytest tests/ # runs 59/59 conformance fixtures
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If `pqcrypto` fails to install (typical on older pip), upgrade pip first:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install --upgrade pip
|
|
58
|
+
pip install -e '.[dev]'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`pqcrypto` requires a C compiler toolchain for source builds; prebuilt wheels exist for most platform / Python combinations.
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from ratify_protocol import (
|
|
67
|
+
generate_human_root, generate_agent,
|
|
68
|
+
DelegationCert, ProofBundle, VerifyOptions,
|
|
69
|
+
PROTOCOL_VERSION, SCOPE_MEETING_ATTEND,
|
|
70
|
+
issue_delegation, sign_challenge, generate_challenge,
|
|
71
|
+
derive_id, verify_bundle, HybridSignature,
|
|
72
|
+
)
|
|
73
|
+
import time
|
|
74
|
+
|
|
75
|
+
# 1. DELEGATE — Alice creates her root and authorizes an agent.
|
|
76
|
+
root, root_priv = generate_human_root()
|
|
77
|
+
agent, agent_priv = generate_agent("Alice's Assistant", "voice_agent")
|
|
78
|
+
|
|
79
|
+
now = int(time.time())
|
|
80
|
+
cert = DelegationCert(
|
|
81
|
+
cert_id="cert-1", version=PROTOCOL_VERSION,
|
|
82
|
+
issuer_id=root.id, issuer_pub_key=root.public_key,
|
|
83
|
+
subject_id=agent.id, subject_pub_key=agent.public_key,
|
|
84
|
+
scope=[SCOPE_MEETING_ATTEND],
|
|
85
|
+
issued_at=now, expires_at=now + 7 * 24 * 3600,
|
|
86
|
+
signature=HybridSignature(ed25519=b"", ml_dsa_65=b""), # filled by issue_delegation
|
|
87
|
+
)
|
|
88
|
+
issue_delegation(cert, root_priv)
|
|
89
|
+
|
|
90
|
+
# 2. PRESENT — agent builds a proof bundle on demand.
|
|
91
|
+
challenge = generate_challenge()
|
|
92
|
+
challenge_at = int(time.time())
|
|
93
|
+
bundle = ProofBundle(
|
|
94
|
+
agent_id=agent.id,
|
|
95
|
+
agent_pub_key=agent.public_key,
|
|
96
|
+
delegations=[cert],
|
|
97
|
+
challenge=challenge,
|
|
98
|
+
challenge_at=challenge_at,
|
|
99
|
+
challenge_sig=sign_challenge(challenge, challenge_at, agent_priv),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# 3. VERIFY — any third party checks the bundle.
|
|
103
|
+
result = verify_bundle(bundle, VerifyOptions(required_scope=SCOPE_MEETING_ATTEND))
|
|
104
|
+
if result.valid:
|
|
105
|
+
print(f"✅ Authorized agent {result.agent_id} for {result.human_id}, scope={result.granted_scope}")
|
|
106
|
+
else:
|
|
107
|
+
print(f"❌ {result.identity_status}: {result.error_reason}")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Key custody
|
|
111
|
+
|
|
112
|
+
The protocol supports three key-custody modes with different trust tradeoffs. See `SPEC.md` §15.2 for the full model.
|
|
113
|
+
|
|
114
|
+
### Self-custody (strongest)
|
|
115
|
+
|
|
116
|
+
The user generates and holds their own keypair. No third party can sign on their behalf.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from ratify_protocol import generate_human_root, issue_delegation
|
|
120
|
+
|
|
121
|
+
# User generates keypair on their own device — private key never leaves
|
|
122
|
+
root, private_key = generate_human_root()
|
|
123
|
+
|
|
124
|
+
# User signs delegations locally
|
|
125
|
+
issue_delegation(cert, private_key)
|
|
126
|
+
|
|
127
|
+
# Only root.id and root.public_key are shared with registries
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Custodial
|
|
131
|
+
|
|
132
|
+
A registry operator generates and stores the keypair server-side (envelope-encrypted with KMS). The user never touches keys directly. The operator calls the same SDK functions on the user's behalf.
|
|
133
|
+
|
|
134
|
+
### Self-custody upgrade
|
|
135
|
+
|
|
136
|
+
A user who started in custodial mode can migrate to self-custody at any time using `KeyRotationStatement`:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from ratify_protocol import (
|
|
140
|
+
generate_human_root,
|
|
141
|
+
issue_key_rotation_statement,
|
|
142
|
+
KeyRotationStatement,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# User generates a NEW keypair on their device
|
|
146
|
+
new_root, new_private_key = generate_human_root()
|
|
147
|
+
|
|
148
|
+
# Rotation statement signed by BOTH old (custodial) and new (device) keys
|
|
149
|
+
stmt = KeyRotationStatement(
|
|
150
|
+
version=1,
|
|
151
|
+
old_id=old_root.id,
|
|
152
|
+
old_pub_key=old_root.public_key,
|
|
153
|
+
new_id=new_root.id,
|
|
154
|
+
new_pub_key=new_root.public_key,
|
|
155
|
+
rotated_at=int(time.time()),
|
|
156
|
+
reason="routine",
|
|
157
|
+
)
|
|
158
|
+
issue_key_rotation_statement(stmt, old_custodial_private_key, new_private_key)
|
|
159
|
+
|
|
160
|
+
# From now on, only the user's device key can sign delegations.
|
|
161
|
+
# Auditors verify continuity via the rotation statement.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Canonical serialization
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from ratify_protocol import canonical_json, delegation_sign_bytes, challenge_sign_bytes
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
These produce byte-identical output to the Go / TS / Rust references. If your application needs to sign Ratify artifacts with custom code, always pass through `canonical_json` for the JSON pieces.
|
|
171
|
+
|
|
172
|
+
## Scope vocabulary
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from ratify_protocol import (
|
|
176
|
+
SCOPE_MEETING_ATTEND, # "meeting:attend"
|
|
177
|
+
SCOPE_FILES_WRITE, # sensitive — never rides a wildcard
|
|
178
|
+
expand_scopes,
|
|
179
|
+
intersect_scopes,
|
|
180
|
+
is_sensitive,
|
|
181
|
+
validate_scopes,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
expand_scopes(["meeting:*"])
|
|
185
|
+
# ['meeting:attend', 'meeting:chat', 'meeting:share_screen', 'meeting:speak', 'meeting:video']
|
|
186
|
+
|
|
187
|
+
intersect_scopes(["meeting:*"], ["meeting:attend", "meeting:speak"])
|
|
188
|
+
# ['meeting:attend', 'meeting:speak']
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Full scope vocabulary at a glance
|
|
192
|
+
|
|
193
|
+
Ratify v1 ships 52 canonical scopes across fourteen domains, plus a `custom:` extension pattern for application-specific scopes. See [`SPEC.md`](../../SPEC.md) §9 for the full table including sensitivity flags and wildcard expansions.
|
|
194
|
+
|
|
195
|
+
For app-specific needs not covered by the canonical vocabulary, use the `custom:` prefix:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from ratify_protocol import CUSTOM_SCOPE_PREFIX, validate_scopes
|
|
199
|
+
|
|
200
|
+
validate_scopes(["custom:acme:inventory:read"]) # → None (valid)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Custom scopes pass through `expand_scopes` unchanged and are non-sensitive by default.
|
|
204
|
+
|
|
205
|
+
## Running the conformance tests
|
|
206
|
+
|
|
207
|
+
From this SDK directory:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
python -m venv .venv && source .venv/bin/activate
|
|
211
|
+
pip install -e .
|
|
212
|
+
pip install pytest
|
|
213
|
+
pytest -v
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The suite loads every fixture at `../../testvectors/v1/*.json` and runs it through the Python implementation. All 59 must pass; any failure means this SDK has drifted from the Go reference.
|
|
217
|
+
|
|
218
|
+
## Notes on the ML-DSA-65 library
|
|
219
|
+
|
|
220
|
+
This SDK uses `pqcrypto` which wraps PQClean's ML-DSA-65 implementation. Two things to be aware of:
|
|
221
|
+
|
|
222
|
+
**Randomized signing.** `pqcrypto`'s default signing mode is randomized (two signings of the same message produce different bytes). This does NOT affect interop: signatures produced here verify correctly in Go, TS, and Rust implementations, and vice versa. The canonical signable bytes (what gets fed into the signature function) are what must match across languages — those do match byte-for-byte.
|
|
223
|
+
|
|
224
|
+
**Non-deterministic keygen from seeds.** `pqcrypto` does not expose seed-based ML-DSA-65 key generation through its public API — `crypto_sign_keypair` reads from the OS RNG internally. This means `hybrid_keypair_from_seeds()` is NOT truly deterministic on the ML-DSA side in Python. The practical consequence: **Python cannot regenerate the canonical test fixtures** (the Go reference does that). Python's conformance contract is verification-only — it verifies Go-generated fixtures byte-for-byte but does not regenerate them. This is a known limitation of the `pqcrypto` library, not a protocol limitation.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
Apache-2.0. See the project-level LICENSE.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# ratify-protocol
|
|
2
|
+
|
|
3
|
+
**Python reference SDK for the Ratify Protocol v1 — a cryptographic trust protocol for human-agent and agent-agent interactions as agents start to transact.**
|
|
4
|
+
|
|
5
|
+
Quantum-safe by design: every signature is hybrid Ed25519 + ML-DSA-65 (NIST FIPS 204). Both must verify.
|
|
6
|
+
|
|
7
|
+
Byte-identical interoperability with the Go, TypeScript, and Rust reference implementations. Validated against the **59 canonical test vectors** on every CI run.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install ratify-protocol
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This pulls in two binary dependencies: `cryptography` (Ed25519 via OpenSSL) and `pqcrypto>=0.3.4` (ML-DSA-65). Both ship wheels for Linux / macOS / Windows on CPython 3.10+.
|
|
16
|
+
|
|
17
|
+
### Running the conformance suite from a clean checkout
|
|
18
|
+
|
|
19
|
+
If you cloned the repo and want to run `python -m pytest` against the committed fixtures, the package is not on your path until you install it. Do this:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd sdks/python
|
|
23
|
+
python -m venv .venv && source .venv/bin/activate
|
|
24
|
+
pip install -e '.[dev]' # installs ratify-protocol + cryptography + pqcrypto + pytest
|
|
25
|
+
python -m pytest tests/ # runs 59/59 conformance fixtures
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If `pqcrypto` fails to install (typical on older pip), upgrade pip first:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install --upgrade pip
|
|
32
|
+
pip install -e '.[dev]'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`pqcrypto` requires a C compiler toolchain for source builds; prebuilt wheels exist for most platform / Python combinations.
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from ratify_protocol import (
|
|
41
|
+
generate_human_root, generate_agent,
|
|
42
|
+
DelegationCert, ProofBundle, VerifyOptions,
|
|
43
|
+
PROTOCOL_VERSION, SCOPE_MEETING_ATTEND,
|
|
44
|
+
issue_delegation, sign_challenge, generate_challenge,
|
|
45
|
+
derive_id, verify_bundle, HybridSignature,
|
|
46
|
+
)
|
|
47
|
+
import time
|
|
48
|
+
|
|
49
|
+
# 1. DELEGATE — Alice creates her root and authorizes an agent.
|
|
50
|
+
root, root_priv = generate_human_root()
|
|
51
|
+
agent, agent_priv = generate_agent("Alice's Assistant", "voice_agent")
|
|
52
|
+
|
|
53
|
+
now = int(time.time())
|
|
54
|
+
cert = DelegationCert(
|
|
55
|
+
cert_id="cert-1", version=PROTOCOL_VERSION,
|
|
56
|
+
issuer_id=root.id, issuer_pub_key=root.public_key,
|
|
57
|
+
subject_id=agent.id, subject_pub_key=agent.public_key,
|
|
58
|
+
scope=[SCOPE_MEETING_ATTEND],
|
|
59
|
+
issued_at=now, expires_at=now + 7 * 24 * 3600,
|
|
60
|
+
signature=HybridSignature(ed25519=b"", ml_dsa_65=b""), # filled by issue_delegation
|
|
61
|
+
)
|
|
62
|
+
issue_delegation(cert, root_priv)
|
|
63
|
+
|
|
64
|
+
# 2. PRESENT — agent builds a proof bundle on demand.
|
|
65
|
+
challenge = generate_challenge()
|
|
66
|
+
challenge_at = int(time.time())
|
|
67
|
+
bundle = ProofBundle(
|
|
68
|
+
agent_id=agent.id,
|
|
69
|
+
agent_pub_key=agent.public_key,
|
|
70
|
+
delegations=[cert],
|
|
71
|
+
challenge=challenge,
|
|
72
|
+
challenge_at=challenge_at,
|
|
73
|
+
challenge_sig=sign_challenge(challenge, challenge_at, agent_priv),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# 3. VERIFY — any third party checks the bundle.
|
|
77
|
+
result = verify_bundle(bundle, VerifyOptions(required_scope=SCOPE_MEETING_ATTEND))
|
|
78
|
+
if result.valid:
|
|
79
|
+
print(f"✅ Authorized agent {result.agent_id} for {result.human_id}, scope={result.granted_scope}")
|
|
80
|
+
else:
|
|
81
|
+
print(f"❌ {result.identity_status}: {result.error_reason}")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Key custody
|
|
85
|
+
|
|
86
|
+
The protocol supports three key-custody modes with different trust tradeoffs. See `SPEC.md` §15.2 for the full model.
|
|
87
|
+
|
|
88
|
+
### Self-custody (strongest)
|
|
89
|
+
|
|
90
|
+
The user generates and holds their own keypair. No third party can sign on their behalf.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from ratify_protocol import generate_human_root, issue_delegation
|
|
94
|
+
|
|
95
|
+
# User generates keypair on their own device — private key never leaves
|
|
96
|
+
root, private_key = generate_human_root()
|
|
97
|
+
|
|
98
|
+
# User signs delegations locally
|
|
99
|
+
issue_delegation(cert, private_key)
|
|
100
|
+
|
|
101
|
+
# Only root.id and root.public_key are shared with registries
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Custodial
|
|
105
|
+
|
|
106
|
+
A registry operator generates and stores the keypair server-side (envelope-encrypted with KMS). The user never touches keys directly. The operator calls the same SDK functions on the user's behalf.
|
|
107
|
+
|
|
108
|
+
### Self-custody upgrade
|
|
109
|
+
|
|
110
|
+
A user who started in custodial mode can migrate to self-custody at any time using `KeyRotationStatement`:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from ratify_protocol import (
|
|
114
|
+
generate_human_root,
|
|
115
|
+
issue_key_rotation_statement,
|
|
116
|
+
KeyRotationStatement,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# User generates a NEW keypair on their device
|
|
120
|
+
new_root, new_private_key = generate_human_root()
|
|
121
|
+
|
|
122
|
+
# Rotation statement signed by BOTH old (custodial) and new (device) keys
|
|
123
|
+
stmt = KeyRotationStatement(
|
|
124
|
+
version=1,
|
|
125
|
+
old_id=old_root.id,
|
|
126
|
+
old_pub_key=old_root.public_key,
|
|
127
|
+
new_id=new_root.id,
|
|
128
|
+
new_pub_key=new_root.public_key,
|
|
129
|
+
rotated_at=int(time.time()),
|
|
130
|
+
reason="routine",
|
|
131
|
+
)
|
|
132
|
+
issue_key_rotation_statement(stmt, old_custodial_private_key, new_private_key)
|
|
133
|
+
|
|
134
|
+
# From now on, only the user's device key can sign delegations.
|
|
135
|
+
# Auditors verify continuity via the rotation statement.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Canonical serialization
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from ratify_protocol import canonical_json, delegation_sign_bytes, challenge_sign_bytes
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
These produce byte-identical output to the Go / TS / Rust references. If your application needs to sign Ratify artifacts with custom code, always pass through `canonical_json` for the JSON pieces.
|
|
145
|
+
|
|
146
|
+
## Scope vocabulary
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from ratify_protocol import (
|
|
150
|
+
SCOPE_MEETING_ATTEND, # "meeting:attend"
|
|
151
|
+
SCOPE_FILES_WRITE, # sensitive — never rides a wildcard
|
|
152
|
+
expand_scopes,
|
|
153
|
+
intersect_scopes,
|
|
154
|
+
is_sensitive,
|
|
155
|
+
validate_scopes,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
expand_scopes(["meeting:*"])
|
|
159
|
+
# ['meeting:attend', 'meeting:chat', 'meeting:share_screen', 'meeting:speak', 'meeting:video']
|
|
160
|
+
|
|
161
|
+
intersect_scopes(["meeting:*"], ["meeting:attend", "meeting:speak"])
|
|
162
|
+
# ['meeting:attend', 'meeting:speak']
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Full scope vocabulary at a glance
|
|
166
|
+
|
|
167
|
+
Ratify v1 ships 52 canonical scopes across fourteen domains, plus a `custom:` extension pattern for application-specific scopes. See [`SPEC.md`](../../SPEC.md) §9 for the full table including sensitivity flags and wildcard expansions.
|
|
168
|
+
|
|
169
|
+
For app-specific needs not covered by the canonical vocabulary, use the `custom:` prefix:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from ratify_protocol import CUSTOM_SCOPE_PREFIX, validate_scopes
|
|
173
|
+
|
|
174
|
+
validate_scopes(["custom:acme:inventory:read"]) # → None (valid)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Custom scopes pass through `expand_scopes` unchanged and are non-sensitive by default.
|
|
178
|
+
|
|
179
|
+
## Running the conformance tests
|
|
180
|
+
|
|
181
|
+
From this SDK directory:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
python -m venv .venv && source .venv/bin/activate
|
|
185
|
+
pip install -e .
|
|
186
|
+
pip install pytest
|
|
187
|
+
pytest -v
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The suite loads every fixture at `../../testvectors/v1/*.json` and runs it through the Python implementation. All 59 must pass; any failure means this SDK has drifted from the Go reference.
|
|
191
|
+
|
|
192
|
+
## Notes on the ML-DSA-65 library
|
|
193
|
+
|
|
194
|
+
This SDK uses `pqcrypto` which wraps PQClean's ML-DSA-65 implementation. Two things to be aware of:
|
|
195
|
+
|
|
196
|
+
**Randomized signing.** `pqcrypto`'s default signing mode is randomized (two signings of the same message produce different bytes). This does NOT affect interop: signatures produced here verify correctly in Go, TS, and Rust implementations, and vice versa. The canonical signable bytes (what gets fed into the signature function) are what must match across languages — those do match byte-for-byte.
|
|
197
|
+
|
|
198
|
+
**Non-deterministic keygen from seeds.** `pqcrypto` does not expose seed-based ML-DSA-65 key generation through its public API — `crypto_sign_keypair` reads from the OS RNG internally. This means `hybrid_keypair_from_seeds()` is NOT truly deterministic on the ML-DSA side in Python. The practical consequence: **Python cannot regenerate the canonical test fixtures** (the Go reference does that). Python's conformance contract is verification-only — it verifies Go-generated fixtures byte-for-byte but does not regenerate them. This is a known limitation of the `pqcrypto` library, not a protocol limitation.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
Apache-2.0. See the project-level LICENSE.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ratify-protocol"
|
|
7
|
+
version = "1.0.0a5"
|
|
8
|
+
description = "Cryptographic trust protocol for human-agent and agent-agent interactions — Python reference SDK."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "Identities AI"}]
|
|
11
|
+
license = {text = "Apache-2.0"}
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["ratify", "delegation", "agent", "ed25519", "ml-dsa", "post-quantum", "identity", "ai-agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Security :: Cryptography",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"cryptography>=44.0",
|
|
26
|
+
"pqcrypto>=0.3.4",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8.0",
|
|
32
|
+
"pytest-cov>=5.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/identities-ai/ratify-protocol"
|
|
37
|
+
Repository = "https://github.com/identities-ai/ratify-protocol"
|
|
38
|
+
Issues = "https://github.com/identities-ai/ratify-protocol/issues"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Ratify Protocol v1 — Python reference SDK.
|
|
2
|
+
|
|
3
|
+
A cryptographic trust protocol for human-agent and agent-agent interactions
|
|
4
|
+
as agents start to transact. Every signature is hybrid Ed25519 + ML-DSA-65
|
|
5
|
+
(FIPS 204): quantum-safe by design.
|
|
6
|
+
|
|
7
|
+
Quickstart:
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from ratify_protocol import (
|
|
11
|
+
generate_human_root, generate_agent,
|
|
12
|
+
DelegationCert, ProofBundle, VerifyOptions,
|
|
13
|
+
PROTOCOL_VERSION, SCOPE_MEETING_ATTEND,
|
|
14
|
+
issue_delegation, sign_challenge, generate_challenge,
|
|
15
|
+
derive_id, verify_bundle,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
root, root_priv = generate_human_root()
|
|
19
|
+
agent, agent_priv = generate_agent("Alice's Assistant", "voice_agent")
|
|
20
|
+
|
|
21
|
+
cert = DelegationCert(
|
|
22
|
+
cert_id="cert-1", version=PROTOCOL_VERSION,
|
|
23
|
+
issuer_id=root.id, issuer_pub_key=root.public_key,
|
|
24
|
+
subject_id=agent.id, subject_pub_key=agent.public_key,
|
|
25
|
+
scope=[SCOPE_MEETING_ATTEND],
|
|
26
|
+
issued_at=0, expires_at=2000000000,
|
|
27
|
+
signature=None, # filled by issue_delegation
|
|
28
|
+
)
|
|
29
|
+
issue_delegation(cert, root_priv)
|
|
30
|
+
# ... then agent builds a ProofBundle, verifier runs verify_bundle(bundle)
|
|
31
|
+
|
|
32
|
+
See docs/EXPLAINED.md and docs/AGENT_TO_AGENT.md for full semantics.
|
|
33
|
+
"""
|
|
34
|
+
from .canonical import (
|
|
35
|
+
base64_standard_decode,
|
|
36
|
+
base64_standard_encode,
|
|
37
|
+
canonical_json,
|
|
38
|
+
hex_decode,
|
|
39
|
+
hex_encode,
|
|
40
|
+
)
|
|
41
|
+
from .crypto import (
|
|
42
|
+
chain_hash,
|
|
43
|
+
challenge_sign_bytes,
|
|
44
|
+
delegation_sign_bytes,
|
|
45
|
+
derive_id,
|
|
46
|
+
generate_agent,
|
|
47
|
+
generate_challenge,
|
|
48
|
+
generate_human_root,
|
|
49
|
+
generate_hybrid_keypair,
|
|
50
|
+
hybrid_keypair_from_seeds,
|
|
51
|
+
issue_delegation,
|
|
52
|
+
issue_key_rotation_statement,
|
|
53
|
+
issue_revocation_list,
|
|
54
|
+
issue_revocation_push,
|
|
55
|
+
issue_session_token,
|
|
56
|
+
issue_witness_entry,
|
|
57
|
+
key_rotation_sign_bytes,
|
|
58
|
+
revocation_push_sign_bytes,
|
|
59
|
+
revocation_sign_bytes,
|
|
60
|
+
session_token_sign_bytes,
|
|
61
|
+
sign_both,
|
|
62
|
+
sign_challenge,
|
|
63
|
+
sign_transaction_receipt_party,
|
|
64
|
+
transaction_receipt_sign_bytes,
|
|
65
|
+
verify_both,
|
|
66
|
+
verify_challenge_signature,
|
|
67
|
+
verify_delegation_signature,
|
|
68
|
+
verify_delegation_signature_e,
|
|
69
|
+
verify_key_rotation_statement,
|
|
70
|
+
verify_key_rotation_statement_e,
|
|
71
|
+
verify_revocation_list,
|
|
72
|
+
verify_revocation_push,
|
|
73
|
+
verify_session_token,
|
|
74
|
+
verify_session_token_e,
|
|
75
|
+
verify_witness_entry,
|
|
76
|
+
witness_entry_sign_bytes,
|
|
77
|
+
)
|
|
78
|
+
from .scope import (
|
|
79
|
+
CUSTOM_SCOPE_PREFIX,
|
|
80
|
+
SCOPE_COMMS_CALENDAR_READ,
|
|
81
|
+
SCOPE_COMMS_CALENDAR_WRITE,
|
|
82
|
+
SCOPE_COMMS_EMAIL_DELETE,
|
|
83
|
+
SCOPE_COMMS_EMAIL_READ,
|
|
84
|
+
SCOPE_COMMS_EMAIL_SEND,
|
|
85
|
+
SCOPE_COMMS_MESSAGE_DELETE,
|
|
86
|
+
SCOPE_COMMS_MESSAGE_READ,
|
|
87
|
+
SCOPE_COMMS_MESSAGE_SEND,
|
|
88
|
+
SCOPE_CONTRACT_READ,
|
|
89
|
+
SCOPE_CONTRACT_SIGN,
|
|
90
|
+
SCOPE_DATA_DELETE,
|
|
91
|
+
SCOPE_DATA_EXPORT,
|
|
92
|
+
SCOPE_DATA_READ,
|
|
93
|
+
SCOPE_DATA_SHARE,
|
|
94
|
+
SCOPE_DATA_WRITE,
|
|
95
|
+
SCOPE_EXECUTE_CODE,
|
|
96
|
+
SCOPE_EXECUTE_TOOL,
|
|
97
|
+
SCOPE_FILES_READ,
|
|
98
|
+
SCOPE_FILES_WRITE,
|
|
99
|
+
SCOPE_GENERATE_CONTENT,
|
|
100
|
+
SCOPE_GENERATE_DEEPFAKE,
|
|
101
|
+
SCOPE_IDENTITY_DELEGATE,
|
|
102
|
+
SCOPE_IDENTITY_PROVE,
|
|
103
|
+
SCOPE_MEETING_ATTEND,
|
|
104
|
+
SCOPE_MEETING_CHAT,
|
|
105
|
+
SCOPE_MEETING_RECORD,
|
|
106
|
+
SCOPE_MEETING_SHARE_SCREEN,
|
|
107
|
+
SCOPE_MEETING_SPEAK,
|
|
108
|
+
SCOPE_MEETING_VIDEO,
|
|
109
|
+
SCOPE_PAYMENTS_AUTHORIZE,
|
|
110
|
+
SCOPE_PAYMENTS_RECEIVE,
|
|
111
|
+
SCOPE_PAYMENTS_SEND,
|
|
112
|
+
SCOPE_TRANSACT_PURCHASE,
|
|
113
|
+
SCOPE_TRANSACT_SELL,
|
|
114
|
+
expand_scopes,
|
|
115
|
+
has_scope,
|
|
116
|
+
intersect_scopes,
|
|
117
|
+
is_sensitive,
|
|
118
|
+
validate_scopes,
|
|
119
|
+
)
|
|
120
|
+
from .types import (
|
|
121
|
+
CHALLENGE_WINDOW_SECONDS,
|
|
122
|
+
ED25519_PUBLIC_KEY_SIZE,
|
|
123
|
+
ED25519_SIGNATURE_SIZE,
|
|
124
|
+
MAX_DELEGATION_CHAIN_DEPTH,
|
|
125
|
+
MLDSA65_PUBLIC_KEY_SIZE,
|
|
126
|
+
MLDSA65_SIGNATURE_SIZE,
|
|
127
|
+
PROTOCOL_VERSION,
|
|
128
|
+
AgentIdentity,
|
|
129
|
+
Anchor,
|
|
130
|
+
Constraint,
|
|
131
|
+
DelegationCert,
|
|
132
|
+
HumanRoot,
|
|
133
|
+
HybridPrivateKey,
|
|
134
|
+
HybridPublicKey,
|
|
135
|
+
HybridSignature,
|
|
136
|
+
IdentityStatus,
|
|
137
|
+
KeyRotationReason,
|
|
138
|
+
KeyRotationStatement,
|
|
139
|
+
ProofBundle,
|
|
140
|
+
ReceiptParty,
|
|
141
|
+
ReceiptPartySignature,
|
|
142
|
+
RevocationList,
|
|
143
|
+
RevocationPush,
|
|
144
|
+
SessionToken,
|
|
145
|
+
StreamContext,
|
|
146
|
+
TransactionReceipt,
|
|
147
|
+
TransactionReceiptResult,
|
|
148
|
+
VerifierContext,
|
|
149
|
+
VerifyOptions,
|
|
150
|
+
VerifyResult,
|
|
151
|
+
WitnessEntry,
|
|
152
|
+
)
|
|
153
|
+
from .verify import verify_bundle, verify_streamed_turn, verify_transaction_receipt
|
|
154
|
+
|
|
155
|
+
__version__ = "1.0.0a5"
|
|
156
|
+
|
|
157
|
+
__all__ = [
|
|
158
|
+
# types
|
|
159
|
+
"PROTOCOL_VERSION", "MAX_DELEGATION_CHAIN_DEPTH", "CHALLENGE_WINDOW_SECONDS",
|
|
160
|
+
"ED25519_PUBLIC_KEY_SIZE", "ED25519_SIGNATURE_SIZE",
|
|
161
|
+
"MLDSA65_PUBLIC_KEY_SIZE", "MLDSA65_SIGNATURE_SIZE",
|
|
162
|
+
"HybridPublicKey", "HybridSignature", "HybridPrivateKey",
|
|
163
|
+
"Anchor", "HumanRoot", "AgentIdentity",
|
|
164
|
+
"DelegationCert", "ProofBundle", "KeyRotationStatement", "KeyRotationReason",
|
|
165
|
+
"VerifyResult", "VerifyOptions", "StreamContext", "SessionToken",
|
|
166
|
+
"RevocationList", "RevocationPush", "WitnessEntry", "IdentityStatus",
|
|
167
|
+
"TransactionReceipt", "ReceiptParty", "ReceiptPartySignature",
|
|
168
|
+
"TransactionReceiptResult",
|
|
169
|
+
# crypto
|
|
170
|
+
"derive_id", "generate_hybrid_keypair", "hybrid_keypair_from_seeds",
|
|
171
|
+
"generate_human_root", "generate_agent",
|
|
172
|
+
"delegation_sign_bytes", "challenge_sign_bytes", "revocation_sign_bytes",
|
|
173
|
+
"key_rotation_sign_bytes", "revocation_push_sign_bytes", "witness_entry_sign_bytes",
|
|
174
|
+
"session_token_sign_bytes", "chain_hash",
|
|
175
|
+
"transaction_receipt_sign_bytes", "sign_transaction_receipt_party",
|
|
176
|
+
"sign_both", "verify_both",
|
|
177
|
+
"issue_delegation", "verify_delegation_signature", "verify_delegation_signature_e",
|
|
178
|
+
"issue_key_rotation_statement", "verify_key_rotation_statement",
|
|
179
|
+
"verify_key_rotation_statement_e",
|
|
180
|
+
"issue_session_token", "verify_session_token", "verify_session_token_e",
|
|
181
|
+
"sign_challenge", "verify_challenge_signature",
|
|
182
|
+
"issue_revocation_list", "verify_revocation_list",
|
|
183
|
+
"issue_revocation_push", "verify_revocation_push",
|
|
184
|
+
"issue_witness_entry", "verify_witness_entry",
|
|
185
|
+
"generate_challenge",
|
|
186
|
+
# scope
|
|
187
|
+
"expand_scopes", "intersect_scopes", "has_scope", "is_sensitive", "validate_scopes",
|
|
188
|
+
# verify
|
|
189
|
+
"verify_bundle", "verify_streamed_turn", "verify_transaction_receipt",
|
|
190
|
+
# canonical / utils
|
|
191
|
+
"canonical_json", "base64_standard_encode", "base64_standard_decode",
|
|
192
|
+
"hex_encode", "hex_decode",
|
|
193
|
+
# all scope constants
|
|
194
|
+
"SCOPE_MEETING_ATTEND", "SCOPE_MEETING_SPEAK", "SCOPE_MEETING_VIDEO",
|
|
195
|
+
"SCOPE_MEETING_CHAT", "SCOPE_MEETING_SHARE_SCREEN", "SCOPE_MEETING_RECORD",
|
|
196
|
+
"SCOPE_COMMS_MESSAGE_READ", "SCOPE_COMMS_MESSAGE_SEND", "SCOPE_COMMS_MESSAGE_DELETE",
|
|
197
|
+
"SCOPE_COMMS_EMAIL_READ", "SCOPE_COMMS_EMAIL_SEND", "SCOPE_COMMS_EMAIL_DELETE",
|
|
198
|
+
"SCOPE_COMMS_CALENDAR_READ", "SCOPE_COMMS_CALENDAR_WRITE",
|
|
199
|
+
"SCOPE_FILES_READ", "SCOPE_FILES_WRITE",
|
|
200
|
+
"SCOPE_IDENTITY_PROVE", "SCOPE_IDENTITY_DELEGATE",
|
|
201
|
+
"SCOPE_TRANSACT_PURCHASE", "SCOPE_TRANSACT_SELL",
|
|
202
|
+
"SCOPE_PAYMENTS_SEND", "SCOPE_PAYMENTS_RECEIVE", "SCOPE_PAYMENTS_AUTHORIZE",
|
|
203
|
+
"SCOPE_CONTRACT_READ", "SCOPE_CONTRACT_SIGN",
|
|
204
|
+
"SCOPE_DATA_READ", "SCOPE_DATA_WRITE", "SCOPE_DATA_DELETE",
|
|
205
|
+
"SCOPE_DATA_EXPORT", "SCOPE_DATA_SHARE",
|
|
206
|
+
"SCOPE_EXECUTE_TOOL", "SCOPE_EXECUTE_CODE",
|
|
207
|
+
"SCOPE_GENERATE_CONTENT", "SCOPE_GENERATE_DEEPFAKE",
|
|
208
|
+
"CUSTOM_SCOPE_PREFIX",
|
|
209
|
+
]
|