rio-receipt-protocol 2.2.0__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.
- rio_receipt_protocol-2.2.0/PKG-INFO +121 -0
- rio_receipt_protocol-2.2.0/README.md +93 -0
- rio_receipt_protocol-2.2.0/pyproject.toml +45 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol/__init__.py +26 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol/ledger.py +106 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol/receipts.py +247 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol/verifier.py +128 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol.egg-info/PKG-INFO +121 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol.egg-info/SOURCES.txt +12 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol.egg-info/dependency_links.txt +1 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol.egg-info/requires.txt +3 -0
- rio_receipt_protocol-2.2.0/rio_receipt_protocol.egg-info/top_level.txt +1 -0
- rio_receipt_protocol-2.2.0/setup.cfg +4 -0
- rio_receipt_protocol-2.2.0/tests/test_conformance.py +211 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rio-receipt-protocol
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Cryptographic proof for AI actions. Generate tamper-evident receipts, maintain hash-chained ledgers, and verify AI action audit trails. Zero dependencies.
|
|
5
|
+
Author: RIO Protocol Contributors
|
|
6
|
+
License: MIT OR Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://rioprotocol-q9cry3ny.manus.space
|
|
8
|
+
Project-URL: Repository, https://github.com/bkr1297-RIO/rio-receipt-protocol
|
|
9
|
+
Project-URL: Issues, https://github.com/bkr1297-RIO/rio-receipt-protocol/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/bkr1297-RIO/rio-receipt-protocol/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai-governance,ai-receipts,cryptographic-proof,tamper-evident,hash-chain,ledger,ai-audit,ai-safety,sha256,ed25519,openai,anthropic,langchain,ai-agent,compliance,audit-trail
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Provides-Extra: signing
|
|
27
|
+
Requires-Dist: pynacl>=1.5.0; extra == "signing"
|
|
28
|
+
|
|
29
|
+
# rio-receipt-protocol
|
|
30
|
+
|
|
31
|
+
Cryptographic proof for AI actions. Open standard. **Zero required dependencies.**
|
|
32
|
+
|
|
33
|
+
Generate tamper-evident receipts, maintain hash-chained ledgers, and verify AI action audit trails — all with nothing more than the Python standard library.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install rio-receipt-protocol
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For Ed25519 signature support (optional):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install rio-receipt-protocol[signing]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from rio_receipt_protocol import (
|
|
51
|
+
hash_intent, hash_execution, generate_receipt, verify_receipt, create_ledger
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 1. Hash the intent
|
|
55
|
+
intent_hash = hash_intent(
|
|
56
|
+
intent_id="i-001",
|
|
57
|
+
action="send_email",
|
|
58
|
+
agent_id="agent-1",
|
|
59
|
+
parameters={"to": "user@example.com", "subject": "Hello"},
|
|
60
|
+
timestamp="2026-04-01T00:00:00.000Z",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 2. Hash the execution
|
|
64
|
+
execution_hash = hash_execution(
|
|
65
|
+
intent_id="i-001",
|
|
66
|
+
action="send_email",
|
|
67
|
+
result="sent",
|
|
68
|
+
connector="smtp",
|
|
69
|
+
timestamp="2026-04-01T00:00:01.000Z",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 3. Generate a receipt
|
|
73
|
+
receipt = generate_receipt(
|
|
74
|
+
intent_hash=intent_hash,
|
|
75
|
+
execution_hash=execution_hash,
|
|
76
|
+
intent_id="i-001",
|
|
77
|
+
action="send_email",
|
|
78
|
+
agent_id="agent-1",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# 4. Verify it
|
|
82
|
+
result = verify_receipt(receipt)
|
|
83
|
+
assert result["valid"] is True
|
|
84
|
+
|
|
85
|
+
# 5. Append to a tamper-evident ledger
|
|
86
|
+
ledger = create_ledger()
|
|
87
|
+
entry = ledger.append(
|
|
88
|
+
intent_id="i-001",
|
|
89
|
+
action="send_email",
|
|
90
|
+
agent_id="agent-1",
|
|
91
|
+
status="executed",
|
|
92
|
+
detail="Email sent",
|
|
93
|
+
receipt_hash=receipt["hash_chain"]["receipt_hash"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# 6. Verify the chain
|
|
97
|
+
chain = ledger.verify_chain()
|
|
98
|
+
assert chain["valid"] is True
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## API Reference
|
|
102
|
+
|
|
103
|
+
The Python package mirrors the Node.js API exactly:
|
|
104
|
+
|
|
105
|
+
| Function | Description |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `sha256(data)` | SHA-256 hash of a string |
|
|
108
|
+
| `hash_intent(...)` | Hash an intent object |
|
|
109
|
+
| `hash_execution(...)` | Hash an execution record |
|
|
110
|
+
| `hash_governance(...)` | Hash a governance decision (optional) |
|
|
111
|
+
| `hash_authorization(...)` | Hash an authorization record (optional) |
|
|
112
|
+
| `generate_receipt(...)` | Generate a v2.2 receipt |
|
|
113
|
+
| `verify_receipt(receipt)` | Verify a receipt's hash chain |
|
|
114
|
+
| `create_ledger(file_path=None)` | Create a tamper-evident ledger |
|
|
115
|
+
| `verify_receipt_standalone(receipt)` | Independent receipt verification |
|
|
116
|
+
| `verify_chain(entries)` | Verify a ledger hash chain |
|
|
117
|
+
| `verify_receipt_batch(receipts)` | Batch-verify multiple receipts |
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
Dual-licensed under MIT and Apache 2.0.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# rio-receipt-protocol
|
|
2
|
+
|
|
3
|
+
Cryptographic proof for AI actions. Open standard. **Zero required dependencies.**
|
|
4
|
+
|
|
5
|
+
Generate tamper-evident receipts, maintain hash-chained ledgers, and verify AI action audit trails — all with nothing more than the Python standard library.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install rio-receipt-protocol
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For Ed25519 signature support (optional):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install rio-receipt-protocol[signing]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from rio_receipt_protocol import (
|
|
23
|
+
hash_intent, hash_execution, generate_receipt, verify_receipt, create_ledger
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# 1. Hash the intent
|
|
27
|
+
intent_hash = hash_intent(
|
|
28
|
+
intent_id="i-001",
|
|
29
|
+
action="send_email",
|
|
30
|
+
agent_id="agent-1",
|
|
31
|
+
parameters={"to": "user@example.com", "subject": "Hello"},
|
|
32
|
+
timestamp="2026-04-01T00:00:00.000Z",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# 2. Hash the execution
|
|
36
|
+
execution_hash = hash_execution(
|
|
37
|
+
intent_id="i-001",
|
|
38
|
+
action="send_email",
|
|
39
|
+
result="sent",
|
|
40
|
+
connector="smtp",
|
|
41
|
+
timestamp="2026-04-01T00:00:01.000Z",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# 3. Generate a receipt
|
|
45
|
+
receipt = generate_receipt(
|
|
46
|
+
intent_hash=intent_hash,
|
|
47
|
+
execution_hash=execution_hash,
|
|
48
|
+
intent_id="i-001",
|
|
49
|
+
action="send_email",
|
|
50
|
+
agent_id="agent-1",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 4. Verify it
|
|
54
|
+
result = verify_receipt(receipt)
|
|
55
|
+
assert result["valid"] is True
|
|
56
|
+
|
|
57
|
+
# 5. Append to a tamper-evident ledger
|
|
58
|
+
ledger = create_ledger()
|
|
59
|
+
entry = ledger.append(
|
|
60
|
+
intent_id="i-001",
|
|
61
|
+
action="send_email",
|
|
62
|
+
agent_id="agent-1",
|
|
63
|
+
status="executed",
|
|
64
|
+
detail="Email sent",
|
|
65
|
+
receipt_hash=receipt["hash_chain"]["receipt_hash"],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# 6. Verify the chain
|
|
69
|
+
chain = ledger.verify_chain()
|
|
70
|
+
assert chain["valid"] is True
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
The Python package mirrors the Node.js API exactly:
|
|
76
|
+
|
|
77
|
+
| Function | Description |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `sha256(data)` | SHA-256 hash of a string |
|
|
80
|
+
| `hash_intent(...)` | Hash an intent object |
|
|
81
|
+
| `hash_execution(...)` | Hash an execution record |
|
|
82
|
+
| `hash_governance(...)` | Hash a governance decision (optional) |
|
|
83
|
+
| `hash_authorization(...)` | Hash an authorization record (optional) |
|
|
84
|
+
| `generate_receipt(...)` | Generate a v2.2 receipt |
|
|
85
|
+
| `verify_receipt(receipt)` | Verify a receipt's hash chain |
|
|
86
|
+
| `create_ledger(file_path=None)` | Create a tamper-evident ledger |
|
|
87
|
+
| `verify_receipt_standalone(receipt)` | Independent receipt verification |
|
|
88
|
+
| `verify_chain(entries)` | Verify a ledger hash chain |
|
|
89
|
+
| `verify_receipt_batch(receipts)` | Batch-verify multiple receipts |
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
Dual-licensed under MIT and Apache 2.0.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rio-receipt-protocol"
|
|
7
|
+
version = "2.2.0"
|
|
8
|
+
description = "Cryptographic proof for AI actions. Generate tamper-evident receipts, maintain hash-chained ledgers, and verify AI action audit trails. Zero dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT OR Apache-2.0"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "RIO Protocol Contributors"}
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"ai-governance", "ai-receipts", "cryptographic-proof", "tamper-evident",
|
|
17
|
+
"hash-chain", "ledger", "ai-audit", "ai-safety", "sha256", "ed25519",
|
|
18
|
+
"openai", "anthropic", "langchain", "ai-agent", "compliance", "audit-trail",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"License :: OSI Approved :: Apache Software License",
|
|
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
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://rioprotocol-q9cry3ny.manus.space"
|
|
37
|
+
Repository = "https://github.com/bkr1297-RIO/rio-receipt-protocol"
|
|
38
|
+
Issues = "https://github.com/bkr1297-RIO/rio-receipt-protocol/issues"
|
|
39
|
+
Changelog = "https://github.com/bkr1297-RIO/rio-receipt-protocol/blob/main/CHANGELOG.md"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
signing = ["pynacl>=1.5.0"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
include = ["rio_receipt_protocol*"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RIO Receipt Protocol — Python Implementation (v2.2)
|
|
3
|
+
|
|
4
|
+
Cryptographic proof for AI actions. Open standard. Zero required dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "2.2.0"
|
|
8
|
+
|
|
9
|
+
from .receipts import (
|
|
10
|
+
sha256, hash_intent, hash_execution, hash_governance, hash_authorization,
|
|
11
|
+
generate_receipt, verify_receipt,
|
|
12
|
+
generate_keypair, sign_receipt,
|
|
13
|
+
)
|
|
14
|
+
from .ledger import GENESIS_HASH, create_ledger
|
|
15
|
+
from .verifier import (
|
|
16
|
+
verify_receipt_standalone, verify_chain,
|
|
17
|
+
verify_receipt_against_ledger, verify_receipt_batch,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"sha256", "hash_intent", "hash_execution", "hash_governance", "hash_authorization",
|
|
22
|
+
"generate_receipt", "verify_receipt",
|
|
23
|
+
"generate_keypair", "sign_receipt",
|
|
24
|
+
"GENESIS_HASH", "create_ledger",
|
|
25
|
+
"verify_receipt_standalone", "verify_chain", "verify_receipt_against_ledger", "verify_receipt_batch",
|
|
26
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RIO Receipt Protocol — Tamper-Evident Ledger
|
|
3
|
+
|
|
4
|
+
Zero external dependencies beyond the Python standard library.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
|
|
12
|
+
from .receipts import sha256
|
|
13
|
+
|
|
14
|
+
GENESIS_HASH = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _canonicalize(entry: dict) -> str:
|
|
18
|
+
return json.dumps(
|
|
19
|
+
{"entry_id": entry["entry_id"], "prev_hash": entry["prev_hash"],
|
|
20
|
+
"timestamp": entry["timestamp"], "intent_id": entry["intent_id"],
|
|
21
|
+
"action": entry["action"], "agent_id": entry["agent_id"],
|
|
22
|
+
"status": entry["status"], "detail": entry["detail"],
|
|
23
|
+
"receipt_hash": entry.get("receipt_hash"),
|
|
24
|
+
"authorization_hash": entry.get("authorization_hash"),
|
|
25
|
+
"intent_hash": entry.get("intent_hash")},
|
|
26
|
+
separators=(",", ":"), ensure_ascii=False,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Ledger:
|
|
31
|
+
def __init__(self, file_path: str = None):
|
|
32
|
+
self._entries = []
|
|
33
|
+
self._current_hash = GENESIS_HASH
|
|
34
|
+
self._file_path = file_path
|
|
35
|
+
if file_path and os.path.exists(file_path):
|
|
36
|
+
try:
|
|
37
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
38
|
+
self._entries = json.load(f)
|
|
39
|
+
if self._entries:
|
|
40
|
+
self._current_hash = self._entries[-1]["ledger_hash"]
|
|
41
|
+
except Exception:
|
|
42
|
+
self._entries = []
|
|
43
|
+
self._current_hash = GENESIS_HASH
|
|
44
|
+
|
|
45
|
+
def _persist(self):
|
|
46
|
+
if not self._file_path:
|
|
47
|
+
return
|
|
48
|
+
os.makedirs(os.path.dirname(self._file_path) or ".", exist_ok=True)
|
|
49
|
+
with open(self._file_path, "w", encoding="utf-8") as f:
|
|
50
|
+
json.dump(self._entries, f, indent=2, ensure_ascii=False)
|
|
51
|
+
|
|
52
|
+
def append(self, intent_id: str, action: str, agent_id: str, status: str, detail: str,
|
|
53
|
+
receipt_hash: str = None, authorization_hash: str = None, intent_hash: str = None) -> dict:
|
|
54
|
+
prev_hash = self._current_hash
|
|
55
|
+
now = datetime.now(timezone.utc)
|
|
56
|
+
timestamp = now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
|
|
57
|
+
entry = {
|
|
58
|
+
"entry_id": str(uuid.uuid4()), "prev_hash": prev_hash, "ledger_hash": None,
|
|
59
|
+
"timestamp": timestamp, "intent_id": intent_id, "action": action,
|
|
60
|
+
"agent_id": agent_id, "status": status, "detail": detail,
|
|
61
|
+
"receipt_hash": receipt_hash, "authorization_hash": authorization_hash,
|
|
62
|
+
"intent_hash": intent_hash,
|
|
63
|
+
}
|
|
64
|
+
entry["ledger_hash"] = sha256(_canonicalize(entry))
|
|
65
|
+
self._current_hash = entry["ledger_hash"]
|
|
66
|
+
self._entries.append(entry)
|
|
67
|
+
self._persist()
|
|
68
|
+
return entry
|
|
69
|
+
|
|
70
|
+
def verify_chain(self) -> dict:
|
|
71
|
+
if not self._entries:
|
|
72
|
+
return {"valid": True, "entries_checked": 0, "first_invalid": None}
|
|
73
|
+
prev = GENESIS_HASH
|
|
74
|
+
for i, e in enumerate(self._entries):
|
|
75
|
+
if e["prev_hash"] != prev:
|
|
76
|
+
return {"valid": False, "entries_checked": i + 1, "first_invalid": i,
|
|
77
|
+
"reason": f"Entry {i} prev_hash mismatch. Expected: {prev}, Got: {e['prev_hash']}"}
|
|
78
|
+
computed = sha256(_canonicalize(e))
|
|
79
|
+
if computed != e["ledger_hash"]:
|
|
80
|
+
return {"valid": False, "entries_checked": i + 1, "first_invalid": i,
|
|
81
|
+
"reason": f"Entry {i} hash mismatch. Computed: {computed}, Stored: {e['ledger_hash']}"}
|
|
82
|
+
prev = e["ledger_hash"]
|
|
83
|
+
return {"valid": True, "entries_checked": len(self._entries), "first_invalid": None}
|
|
84
|
+
|
|
85
|
+
def get_entries(self, limit: int = 100, offset: int = 0) -> list:
|
|
86
|
+
return self._entries[offset:offset + limit]
|
|
87
|
+
|
|
88
|
+
def get_entries_by_intent(self, intent_id: str) -> list:
|
|
89
|
+
return [e for e in self._entries if e["intent_id"] == intent_id]
|
|
90
|
+
|
|
91
|
+
def get_entry_count(self) -> int:
|
|
92
|
+
return len(self._entries)
|
|
93
|
+
|
|
94
|
+
def get_current_hash(self) -> str:
|
|
95
|
+
return self._current_hash
|
|
96
|
+
|
|
97
|
+
def get_latest_entry(self) -> dict:
|
|
98
|
+
return self._entries[-1] if self._entries else None
|
|
99
|
+
|
|
100
|
+
def export(self) -> list:
|
|
101
|
+
return json.loads(json.dumps(self._entries))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def create_ledger(file_path: str = None) -> Ledger:
|
|
105
|
+
"""Create a new Ledger instance."""
|
|
106
|
+
return Ledger(file_path=file_path)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RIO Receipt Protocol — Receipt Generation and Verification
|
|
3
|
+
|
|
4
|
+
Zero external dependencies beyond the Python standard library.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sha256(data: str) -> str:
|
|
14
|
+
"""Compute SHA-256 hash of a string. Returns 64-char lowercase hex."""
|
|
15
|
+
return hashlib.sha256(data.encode("utf-8")).hexdigest()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def hash_intent(intent_id: str, action: str, agent_id: str, parameters: dict, timestamp: str) -> str:
|
|
19
|
+
"""Hash an intent object using canonical field order."""
|
|
20
|
+
canonical = json.dumps(
|
|
21
|
+
{"intent_id": intent_id, "action": action, "agent_id": agent_id,
|
|
22
|
+
"parameters": parameters, "timestamp": timestamp},
|
|
23
|
+
separators=(",", ":"), ensure_ascii=False,
|
|
24
|
+
)
|
|
25
|
+
return sha256(canonical)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def hash_execution(intent_id: str, action: str, result: object, connector: str, timestamp: str) -> str:
|
|
29
|
+
"""Hash an execution record using canonical field order."""
|
|
30
|
+
canonical = json.dumps(
|
|
31
|
+
{"intent_id": intent_id, "action": action, "result": result,
|
|
32
|
+
"connector": connector, "timestamp": timestamp},
|
|
33
|
+
separators=(",", ":"), ensure_ascii=False,
|
|
34
|
+
)
|
|
35
|
+
return sha256(canonical)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def hash_governance(intent_id: str, status: str, risk_level: str, requires_approval: bool, checks: list) -> str:
|
|
39
|
+
"""Hash a governance decision. Optional — only for governed receipts."""
|
|
40
|
+
canonical = json.dumps(
|
|
41
|
+
{"intent_id": intent_id, "status": status, "risk_level": risk_level,
|
|
42
|
+
"requires_approval": requires_approval, "checks": checks},
|
|
43
|
+
separators=(",", ":"), ensure_ascii=False,
|
|
44
|
+
)
|
|
45
|
+
return sha256(canonical)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def hash_authorization(intent_id: str, decision: str, authorized_by: str, timestamp: str, conditions=None) -> str:
|
|
49
|
+
"""Hash an authorization record. Optional — only for governed receipts."""
|
|
50
|
+
canonical = json.dumps(
|
|
51
|
+
{"intent_id": intent_id, "decision": decision, "authorized_by": authorized_by,
|
|
52
|
+
"timestamp": timestamp, "conditions": conditions},
|
|
53
|
+
separators=(",", ":"), ensure_ascii=False,
|
|
54
|
+
)
|
|
55
|
+
return sha256(canonical)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _build_chain_order(data: dict) -> list:
|
|
59
|
+
order = ["intent_hash"]
|
|
60
|
+
if data.get("governance_hash"):
|
|
61
|
+
order.append("governance_hash")
|
|
62
|
+
if data.get("authorization_hash"):
|
|
63
|
+
order.append("authorization_hash")
|
|
64
|
+
order.append("execution_hash")
|
|
65
|
+
order.append("receipt_hash")
|
|
66
|
+
return order
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def generate_receipt(
|
|
70
|
+
intent_hash: str, execution_hash: str, intent_id: str, action: str, agent_id: str,
|
|
71
|
+
governance_hash: str = None, authorization_hash: str = None,
|
|
72
|
+
authorized_by: str = None, receipt_type: str = None,
|
|
73
|
+
ingestion: dict = None, identity_binding: dict = None,
|
|
74
|
+
) -> dict:
|
|
75
|
+
"""Generate a RIO Receipt (v2.2)."""
|
|
76
|
+
receipt_id = str(uuid.uuid4())
|
|
77
|
+
now = datetime.now(timezone.utc)
|
|
78
|
+
timestamp = now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
|
|
79
|
+
|
|
80
|
+
is_governed = bool(governance_hash and authorization_hash)
|
|
81
|
+
if receipt_type is None:
|
|
82
|
+
receipt_type = "governed_action" if is_governed else "action"
|
|
83
|
+
|
|
84
|
+
data = {"intent_hash": intent_hash, "execution_hash": execution_hash,
|
|
85
|
+
"governance_hash": governance_hash, "authorization_hash": authorization_hash}
|
|
86
|
+
chain_order = _build_chain_order(data)
|
|
87
|
+
|
|
88
|
+
receipt_content = {"receipt_id": receipt_id}
|
|
89
|
+
for field in chain_order:
|
|
90
|
+
if field != "receipt_hash":
|
|
91
|
+
receipt_content[field] = data[field]
|
|
92
|
+
receipt_content["timestamp"] = timestamp
|
|
93
|
+
receipt_hash = sha256(json.dumps(receipt_content, separators=(",", ":"), ensure_ascii=False))
|
|
94
|
+
|
|
95
|
+
receipt = {
|
|
96
|
+
"receipt_id": receipt_id, "receipt_type": receipt_type,
|
|
97
|
+
"intent_id": intent_id, "action": action, "agent_id": agent_id,
|
|
98
|
+
"authorized_by": authorized_by, "timestamp": timestamp,
|
|
99
|
+
"hash_chain": {
|
|
100
|
+
"intent_hash": intent_hash, "governance_hash": governance_hash,
|
|
101
|
+
"authorization_hash": authorization_hash, "execution_hash": execution_hash,
|
|
102
|
+
"receipt_hash": receipt_hash,
|
|
103
|
+
},
|
|
104
|
+
"verification": {
|
|
105
|
+
"algorithm": "SHA-256", "chain_length": len(chain_order), "chain_order": chain_order,
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ingestion:
|
|
110
|
+
receipt["ingestion"] = {
|
|
111
|
+
"source": ingestion.get("source"), "channel": ingestion.get("channel"),
|
|
112
|
+
"source_message_id": ingestion.get("source_message_id"),
|
|
113
|
+
"timestamp": ingestion.get("timestamp", timestamp),
|
|
114
|
+
}
|
|
115
|
+
if identity_binding:
|
|
116
|
+
receipt["identity_binding"] = {
|
|
117
|
+
"signer_id": identity_binding.get("signer_id"),
|
|
118
|
+
"public_key_hex": identity_binding.get("public_key_hex"),
|
|
119
|
+
"signature_payload_hash": identity_binding.get("signature_payload_hash"),
|
|
120
|
+
"verification_method": identity_binding.get("verification_method"),
|
|
121
|
+
"ed25519_signed": identity_binding.get("ed25519_signed", False),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return receipt
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def verify_receipt(receipt: dict) -> dict:
|
|
128
|
+
"""Verify a receipt by recomputing the receipt hash from its components."""
|
|
129
|
+
chain_order = receipt.get("verification", {}).get("chain_order",
|
|
130
|
+
["intent_hash", "execution_hash", "receipt_hash"])
|
|
131
|
+
|
|
132
|
+
receipt_content = {"receipt_id": receipt["receipt_id"]}
|
|
133
|
+
for field in chain_order:
|
|
134
|
+
if field != "receipt_hash":
|
|
135
|
+
receipt_content[field] = receipt["hash_chain"][field]
|
|
136
|
+
receipt_content["timestamp"] = receipt["timestamp"]
|
|
137
|
+
|
|
138
|
+
computed_hash = sha256(json.dumps(receipt_content, separators=(",", ":"), ensure_ascii=False))
|
|
139
|
+
stored_hash = receipt["hash_chain"]["receipt_hash"]
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"valid": computed_hash == stored_hash,
|
|
143
|
+
"computed_hash": computed_hash, "stored_hash": stored_hash,
|
|
144
|
+
"receipt_id": receipt["receipt_id"],
|
|
145
|
+
"receipt_type": receipt.get("receipt_type", "action"),
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ─── Ed25519 Signing (optional — requires no external dependencies) ──────────
|
|
150
|
+
|
|
151
|
+
def generate_keypair() -> dict:
|
|
152
|
+
"""
|
|
153
|
+
Generate an Ed25519 keypair using Python's standard library (3.12+)
|
|
154
|
+
or PyNaCl as fallback.
|
|
155
|
+
|
|
156
|
+
Returns dict with: private_key_hex, public_key_hex, private_key_obj
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
# Python 3.12+ has Ed25519 in the standard library
|
|
160
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
161
|
+
from cryptography.hazmat.primitives.serialization import (
|
|
162
|
+
Encoding, NoEncryption, PrivateFormat, PublicFormat,
|
|
163
|
+
)
|
|
164
|
+
private_key = Ed25519PrivateKey.generate()
|
|
165
|
+
private_bytes = private_key.private_bytes(
|
|
166
|
+
Encoding.Raw, PrivateFormat.Raw, NoEncryption(),
|
|
167
|
+
)
|
|
168
|
+
public_bytes = private_key.public_key().public_bytes(
|
|
169
|
+
Encoding.Raw, PublicFormat.Raw,
|
|
170
|
+
)
|
|
171
|
+
return {
|
|
172
|
+
"private_key_hex": private_bytes.hex(),
|
|
173
|
+
"public_key_hex": public_bytes.hex(),
|
|
174
|
+
"private_key_obj": private_key,
|
|
175
|
+
}
|
|
176
|
+
except ImportError:
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
from nacl.signing import SigningKey
|
|
181
|
+
from nacl.encoding import HexEncoder
|
|
182
|
+
signing_key = SigningKey.generate()
|
|
183
|
+
return {
|
|
184
|
+
"private_key_hex": signing_key.encode(HexEncoder).decode(),
|
|
185
|
+
"public_key_hex": signing_key.verify_key.encode(HexEncoder).decode(),
|
|
186
|
+
"private_key_obj": signing_key,
|
|
187
|
+
}
|
|
188
|
+
except ImportError:
|
|
189
|
+
raise ImportError(
|
|
190
|
+
"Ed25519 signing requires either 'cryptography' or 'PyNaCl'. "
|
|
191
|
+
"Install one: pip install cryptography OR pip install pynacl"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def sign_receipt(receipt: dict, private_key_obj, public_key_hex: str, signer_id: str) -> dict:
|
|
196
|
+
"""
|
|
197
|
+
Sign a receipt with Ed25519.
|
|
198
|
+
|
|
199
|
+
The signed payload is the UTF-8 encoding of the 64-character hex
|
|
200
|
+
receipt_hash string (per spec Section 4.2). The signature is stored
|
|
201
|
+
in identity_binding.signature_hex as a lowercase hex string.
|
|
202
|
+
|
|
203
|
+
This function mutates the receipt in place and also returns it.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
receipt: A generated RIO Receipt (must have hash_chain.receipt_hash)
|
|
207
|
+
private_key_obj: Ed25519 private key object (from generate_keypair)
|
|
208
|
+
public_key_hex: 64-char hex-encoded public key
|
|
209
|
+
signer_id: Identifier of the signing authority
|
|
210
|
+
"""
|
|
211
|
+
receipt_hash = receipt.get("hash_chain", {}).get("receipt_hash", "")
|
|
212
|
+
if not receipt_hash or len(receipt_hash) != 64:
|
|
213
|
+
raise ValueError("Cannot sign: receipt has no valid receipt_hash")
|
|
214
|
+
|
|
215
|
+
payload = receipt_hash.encode("utf-8")
|
|
216
|
+
|
|
217
|
+
# Try cryptography library first, then PyNaCl
|
|
218
|
+
try:
|
|
219
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
220
|
+
if isinstance(private_key_obj, Ed25519PrivateKey):
|
|
221
|
+
signature_bytes = private_key_obj.sign(payload)
|
|
222
|
+
signature_hex = signature_bytes.hex()
|
|
223
|
+
else:
|
|
224
|
+
raise TypeError("Not a cryptography key")
|
|
225
|
+
except (ImportError, TypeError):
|
|
226
|
+
from nacl.signing import SigningKey
|
|
227
|
+
if isinstance(private_key_obj, SigningKey):
|
|
228
|
+
signed = private_key_obj.sign(payload)
|
|
229
|
+
signature_hex = signed.signature.hex()
|
|
230
|
+
else:
|
|
231
|
+
raise TypeError(
|
|
232
|
+
"private_key_obj must be an Ed25519PrivateKey (cryptography) "
|
|
233
|
+
"or SigningKey (PyNaCl)"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
from datetime import datetime, timezone
|
|
237
|
+
receipt["identity_binding"] = {
|
|
238
|
+
"signer_id": signer_id,
|
|
239
|
+
"public_key_hex": public_key_hex,
|
|
240
|
+
"signature_hex": signature_hex,
|
|
241
|
+
"signature_payload_hash": receipt_hash,
|
|
242
|
+
"signed_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.") +
|
|
243
|
+
f"{datetime.now(timezone.utc).microsecond // 1000:03d}Z",
|
|
244
|
+
"verification_method": "ed25519-nacl",
|
|
245
|
+
"ed25519_signed": True,
|
|
246
|
+
}
|
|
247
|
+
return receipt
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RIO Receipt Protocol — Standalone Verifier
|
|
3
|
+
|
|
4
|
+
Zero external dependencies beyond the Python standard library.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from .receipts import sha256
|
|
11
|
+
|
|
12
|
+
GENESIS_HASH = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
13
|
+
_HEX64 = re.compile(r"^[a-f0-9]{64}$")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def verify_receipt_standalone(receipt: dict) -> dict:
|
|
17
|
+
"""Verify a single RIO Receipt (proof-layer or governed)."""
|
|
18
|
+
errors = []
|
|
19
|
+
if not receipt.get("receipt_id"):
|
|
20
|
+
errors.append("Missing receipt_id")
|
|
21
|
+
if not receipt.get("timestamp"):
|
|
22
|
+
errors.append("Missing timestamp")
|
|
23
|
+
if not receipt.get("hash_chain"):
|
|
24
|
+
errors.append("Missing hash_chain")
|
|
25
|
+
if not receipt.get("hash_chain"):
|
|
26
|
+
return {"valid": False, "receipt_id": receipt.get("receipt_id", "unknown"), "errors": errors}
|
|
27
|
+
|
|
28
|
+
hc = receipt["hash_chain"]
|
|
29
|
+
for field in ("intent_hash", "execution_hash", "receipt_hash"):
|
|
30
|
+
if not hc.get(field):
|
|
31
|
+
errors.append(f"Missing hash_chain.{field}")
|
|
32
|
+
elif not _HEX64.match(hc[field]):
|
|
33
|
+
errors.append(f"Invalid hash format for hash_chain.{field}")
|
|
34
|
+
for field in ("governance_hash", "authorization_hash"):
|
|
35
|
+
val = hc.get(field)
|
|
36
|
+
if val is not None and val and not _HEX64.match(val):
|
|
37
|
+
errors.append(f"Invalid hash format for hash_chain.{field}")
|
|
38
|
+
if errors:
|
|
39
|
+
return {"valid": False, "receipt_id": receipt.get("receipt_id", "unknown"), "errors": errors}
|
|
40
|
+
|
|
41
|
+
chain_order = receipt.get("verification", {}).get("chain_order",
|
|
42
|
+
["intent_hash", "execution_hash", "receipt_hash"])
|
|
43
|
+
receipt_content = {"receipt_id": receipt["receipt_id"]}
|
|
44
|
+
for field in chain_order:
|
|
45
|
+
if field != "receipt_hash":
|
|
46
|
+
receipt_content[field] = hc[field]
|
|
47
|
+
receipt_content["timestamp"] = receipt["timestamp"]
|
|
48
|
+
computed_hash = sha256(json.dumps(receipt_content, separators=(",", ":"), ensure_ascii=False))
|
|
49
|
+
stored_hash = hc["receipt_hash"]
|
|
50
|
+
|
|
51
|
+
verification = receipt.get("verification", {})
|
|
52
|
+
if verification.get("algorithm") and verification["algorithm"] != "SHA-256":
|
|
53
|
+
errors.append(f"Unexpected algorithm: {verification['algorithm']}")
|
|
54
|
+
if verification.get("chain_length") is not None and verification["chain_length"] != len(chain_order):
|
|
55
|
+
errors.append(f"chain_length {verification['chain_length']} != chain_order length {len(chain_order)}")
|
|
56
|
+
|
|
57
|
+
hash_valid = computed_hash == stored_hash
|
|
58
|
+
if not hash_valid:
|
|
59
|
+
errors.append(f"Receipt hash mismatch: computed {computed_hash}, stored {stored_hash}")
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"valid": hash_valid and len(errors) == 0,
|
|
63
|
+
"receipt_id": receipt["receipt_id"],
|
|
64
|
+
"receipt_type": receipt.get("receipt_type", "action"),
|
|
65
|
+
"computed_hash": computed_hash, "stored_hash": stored_hash,
|
|
66
|
+
"chain_length": len(chain_order), "errors": errors,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def verify_chain(entries: list) -> dict:
|
|
71
|
+
"""Verify a ledger hash chain."""
|
|
72
|
+
if not isinstance(entries, list):
|
|
73
|
+
return {"valid": False, "entries_checked": 0, "first_invalid": None, "reason": "Input is not a list"}
|
|
74
|
+
if not entries:
|
|
75
|
+
return {"valid": True, "entries_checked": 0, "first_invalid": None}
|
|
76
|
+
prev = GENESIS_HASH
|
|
77
|
+
for i, e in enumerate(entries):
|
|
78
|
+
if e.get("prev_hash") != prev:
|
|
79
|
+
return {"valid": False, "entries_checked": i + 1, "first_invalid": i,
|
|
80
|
+
"reason": f"Entry {i} ({e.get('entry_id')}) prev_hash mismatch."}
|
|
81
|
+
canonical = json.dumps(
|
|
82
|
+
{"entry_id": e["entry_id"], "prev_hash": e["prev_hash"],
|
|
83
|
+
"timestamp": e["timestamp"], "intent_id": e["intent_id"],
|
|
84
|
+
"action": e["action"], "agent_id": e["agent_id"],
|
|
85
|
+
"status": e["status"], "detail": e["detail"],
|
|
86
|
+
"receipt_hash": e.get("receipt_hash"),
|
|
87
|
+
"authorization_hash": e.get("authorization_hash"),
|
|
88
|
+
"intent_hash": e.get("intent_hash")},
|
|
89
|
+
separators=(",", ":"), ensure_ascii=False,
|
|
90
|
+
)
|
|
91
|
+
computed = sha256(canonical)
|
|
92
|
+
if computed != e.get("ledger_hash"):
|
|
93
|
+
return {"valid": False, "entries_checked": i + 1, "first_invalid": i,
|
|
94
|
+
"reason": f"Entry {i} ({e.get('entry_id')}) hash mismatch."}
|
|
95
|
+
prev = e["ledger_hash"]
|
|
96
|
+
return {"valid": True, "entries_checked": len(entries), "first_invalid": None, "chain_tip": prev}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def verify_receipt_against_ledger(receipt: dict, ledger_entry: dict) -> dict:
|
|
100
|
+
"""Cross-verify a receipt against its ledger entry."""
|
|
101
|
+
receipt_result = verify_receipt_standalone(receipt)
|
|
102
|
+
errors = list(receipt_result["errors"])
|
|
103
|
+
if ledger_entry.get("receipt_hash") != receipt["hash_chain"]["receipt_hash"]:
|
|
104
|
+
errors.append("Ledger entry receipt_hash does not match receipt")
|
|
105
|
+
if ledger_entry.get("intent_id") != receipt.get("intent_id"):
|
|
106
|
+
errors.append("Intent ID mismatch")
|
|
107
|
+
if ledger_entry.get("intent_hash") and ledger_entry["intent_hash"] != receipt["hash_chain"]["intent_hash"]:
|
|
108
|
+
errors.append("Intent hash mismatch")
|
|
109
|
+
return {
|
|
110
|
+
"valid": receipt_result["valid"] and len(errors) == len(receipt_result["errors"]),
|
|
111
|
+
"receipt_id": receipt["receipt_id"],
|
|
112
|
+
"entry_id": ledger_entry.get("entry_id"),
|
|
113
|
+
"receipt_valid": receipt_result["valid"],
|
|
114
|
+
"cross_references_valid": len(errors) == len(receipt_result["errors"]),
|
|
115
|
+
"errors": errors,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def verify_receipt_batch(receipts: list) -> dict:
|
|
120
|
+
"""Verify multiple receipts in batch."""
|
|
121
|
+
results = [verify_receipt_standalone(r) for r in receipts]
|
|
122
|
+
valid_count = sum(1 for r in results if r["valid"])
|
|
123
|
+
return {
|
|
124
|
+
"total": len(receipts), "valid": valid_count,
|
|
125
|
+
"invalid": len(results) - valid_count,
|
|
126
|
+
"all_valid": all(r["valid"] for r in results),
|
|
127
|
+
"results": results,
|
|
128
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rio-receipt-protocol
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Cryptographic proof for AI actions. Generate tamper-evident receipts, maintain hash-chained ledgers, and verify AI action audit trails. Zero dependencies.
|
|
5
|
+
Author: RIO Protocol Contributors
|
|
6
|
+
License: MIT OR Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://rioprotocol-q9cry3ny.manus.space
|
|
8
|
+
Project-URL: Repository, https://github.com/bkr1297-RIO/rio-receipt-protocol
|
|
9
|
+
Project-URL: Issues, https://github.com/bkr1297-RIO/rio-receipt-protocol/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/bkr1297-RIO/rio-receipt-protocol/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai-governance,ai-receipts,cryptographic-proof,tamper-evident,hash-chain,ledger,ai-audit,ai-safety,sha256,ed25519,openai,anthropic,langchain,ai-agent,compliance,audit-trail
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Provides-Extra: signing
|
|
27
|
+
Requires-Dist: pynacl>=1.5.0; extra == "signing"
|
|
28
|
+
|
|
29
|
+
# rio-receipt-protocol
|
|
30
|
+
|
|
31
|
+
Cryptographic proof for AI actions. Open standard. **Zero required dependencies.**
|
|
32
|
+
|
|
33
|
+
Generate tamper-evident receipts, maintain hash-chained ledgers, and verify AI action audit trails — all with nothing more than the Python standard library.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install rio-receipt-protocol
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For Ed25519 signature support (optional):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install rio-receipt-protocol[signing]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from rio_receipt_protocol import (
|
|
51
|
+
hash_intent, hash_execution, generate_receipt, verify_receipt, create_ledger
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 1. Hash the intent
|
|
55
|
+
intent_hash = hash_intent(
|
|
56
|
+
intent_id="i-001",
|
|
57
|
+
action="send_email",
|
|
58
|
+
agent_id="agent-1",
|
|
59
|
+
parameters={"to": "user@example.com", "subject": "Hello"},
|
|
60
|
+
timestamp="2026-04-01T00:00:00.000Z",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 2. Hash the execution
|
|
64
|
+
execution_hash = hash_execution(
|
|
65
|
+
intent_id="i-001",
|
|
66
|
+
action="send_email",
|
|
67
|
+
result="sent",
|
|
68
|
+
connector="smtp",
|
|
69
|
+
timestamp="2026-04-01T00:00:01.000Z",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 3. Generate a receipt
|
|
73
|
+
receipt = generate_receipt(
|
|
74
|
+
intent_hash=intent_hash,
|
|
75
|
+
execution_hash=execution_hash,
|
|
76
|
+
intent_id="i-001",
|
|
77
|
+
action="send_email",
|
|
78
|
+
agent_id="agent-1",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# 4. Verify it
|
|
82
|
+
result = verify_receipt(receipt)
|
|
83
|
+
assert result["valid"] is True
|
|
84
|
+
|
|
85
|
+
# 5. Append to a tamper-evident ledger
|
|
86
|
+
ledger = create_ledger()
|
|
87
|
+
entry = ledger.append(
|
|
88
|
+
intent_id="i-001",
|
|
89
|
+
action="send_email",
|
|
90
|
+
agent_id="agent-1",
|
|
91
|
+
status="executed",
|
|
92
|
+
detail="Email sent",
|
|
93
|
+
receipt_hash=receipt["hash_chain"]["receipt_hash"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# 6. Verify the chain
|
|
97
|
+
chain = ledger.verify_chain()
|
|
98
|
+
assert chain["valid"] is True
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## API Reference
|
|
102
|
+
|
|
103
|
+
The Python package mirrors the Node.js API exactly:
|
|
104
|
+
|
|
105
|
+
| Function | Description |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `sha256(data)` | SHA-256 hash of a string |
|
|
108
|
+
| `hash_intent(...)` | Hash an intent object |
|
|
109
|
+
| `hash_execution(...)` | Hash an execution record |
|
|
110
|
+
| `hash_governance(...)` | Hash a governance decision (optional) |
|
|
111
|
+
| `hash_authorization(...)` | Hash an authorization record (optional) |
|
|
112
|
+
| `generate_receipt(...)` | Generate a v2.2 receipt |
|
|
113
|
+
| `verify_receipt(receipt)` | Verify a receipt's hash chain |
|
|
114
|
+
| `create_ledger(file_path=None)` | Create a tamper-evident ledger |
|
|
115
|
+
| `verify_receipt_standalone(receipt)` | Independent receipt verification |
|
|
116
|
+
| `verify_chain(entries)` | Verify a ledger hash chain |
|
|
117
|
+
| `verify_receipt_batch(receipts)` | Batch-verify multiple receipts |
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
Dual-licensed under MIT and Apache 2.0.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
rio_receipt_protocol/__init__.py
|
|
4
|
+
rio_receipt_protocol/ledger.py
|
|
5
|
+
rio_receipt_protocol/receipts.py
|
|
6
|
+
rio_receipt_protocol/verifier.py
|
|
7
|
+
rio_receipt_protocol.egg-info/PKG-INFO
|
|
8
|
+
rio_receipt_protocol.egg-info/SOURCES.txt
|
|
9
|
+
rio_receipt_protocol.egg-info/dependency_links.txt
|
|
10
|
+
rio_receipt_protocol.egg-info/requires.txt
|
|
11
|
+
rio_receipt_protocol.egg-info/top_level.txt
|
|
12
|
+
tests/test_conformance.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rio_receipt_protocol
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RIO Receipt Protocol v2.2 — Python Conformance Tests
|
|
3
|
+
|
|
4
|
+
Mirrors the Node.js conformance test suite (tests/conformance.test.mjs).
|
|
5
|
+
Tests proof-layer receipts, governed receipts, hash integrity, ledger
|
|
6
|
+
operations, cross-verification, batch verification, mixed types, and
|
|
7
|
+
optional extensions.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import copy
|
|
14
|
+
|
|
15
|
+
# Add parent to path for imports
|
|
16
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
17
|
+
|
|
18
|
+
from rio_receipt_protocol import (
|
|
19
|
+
sha256,
|
|
20
|
+
hash_intent,
|
|
21
|
+
hash_execution,
|
|
22
|
+
hash_governance,
|
|
23
|
+
hash_authorization,
|
|
24
|
+
generate_receipt,
|
|
25
|
+
verify_receipt,
|
|
26
|
+
create_ledger,
|
|
27
|
+
verify_receipt_standalone,
|
|
28
|
+
verify_chain,
|
|
29
|
+
verify_receipt_against_ledger,
|
|
30
|
+
verify_receipt_batch,
|
|
31
|
+
GENESIS_HASH,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
passed = 0
|
|
35
|
+
failed = 0
|
|
36
|
+
total = 0
|
|
37
|
+
HEX64 = re.compile(r"^[a-f0-9]{64}$")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test(name, condition):
|
|
41
|
+
global passed, failed, total
|
|
42
|
+
total += 1
|
|
43
|
+
if condition:
|
|
44
|
+
passed += 1
|
|
45
|
+
print(f" \033[32m✓\033[0m {name}")
|
|
46
|
+
else:
|
|
47
|
+
failed += 1
|
|
48
|
+
print(f" \033[31m✗\033[0m {name}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def section(name):
|
|
52
|
+
print(f"\n{name}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
INTENT = {
|
|
56
|
+
"intent_id": "test-intent-001",
|
|
57
|
+
"action": "send_email",
|
|
58
|
+
"agent_id": "test-agent-001",
|
|
59
|
+
"parameters": {"to": "user@example.com", "subject": "Test"},
|
|
60
|
+
"timestamp": "2026-04-01T00:00:00.000Z",
|
|
61
|
+
}
|
|
62
|
+
EXECUTION = {
|
|
63
|
+
"intent_id": "test-intent-001",
|
|
64
|
+
"action": "send_email",
|
|
65
|
+
"result": "sent",
|
|
66
|
+
"connector": "smtp",
|
|
67
|
+
"timestamp": "2026-04-01T00:00:01.000Z",
|
|
68
|
+
}
|
|
69
|
+
GOVERNANCE = {
|
|
70
|
+
"intent_id": "test-intent-001",
|
|
71
|
+
"status": "approved",
|
|
72
|
+
"risk_level": "low",
|
|
73
|
+
"requires_approval": False,
|
|
74
|
+
"checks": ["policy_check", "rate_limit"],
|
|
75
|
+
}
|
|
76
|
+
AUTHORIZATION = {
|
|
77
|
+
"intent_id": "test-intent-001",
|
|
78
|
+
"decision": "approved",
|
|
79
|
+
"authorized_by": "HUMAN:admin@example.com",
|
|
80
|
+
"timestamp": "2026-04-01T00:00:00.500Z",
|
|
81
|
+
"conditions": None,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
section("1. Proof-Layer Receipt Generation")
|
|
85
|
+
intent_hash = hash_intent(**INTENT)
|
|
86
|
+
execution_hash = hash_execution(**EXECUTION)
|
|
87
|
+
proof_receipt = generate_receipt(
|
|
88
|
+
intent_hash=intent_hash, execution_hash=execution_hash,
|
|
89
|
+
intent_id=INTENT["intent_id"], action=INTENT["action"], agent_id=INTENT["agent_id"],
|
|
90
|
+
)
|
|
91
|
+
test("generates a proof-layer receipt with 3-hash chain", proof_receipt["verification"]["chain_length"] == 3)
|
|
92
|
+
test("proof-layer receipt has all required core fields",
|
|
93
|
+
all(proof_receipt.get(f) is not None for f in
|
|
94
|
+
["receipt_id", "receipt_type", "intent_id", "action", "agent_id", "timestamp", "hash_chain", "verification"]))
|
|
95
|
+
test("proof-layer chain_order is [intent_hash, execution_hash, receipt_hash]",
|
|
96
|
+
proof_receipt["verification"]["chain_order"] == ["intent_hash", "execution_hash", "receipt_hash"])
|
|
97
|
+
test("proof-layer receipt self-verifies", verify_receipt(proof_receipt)["valid"])
|
|
98
|
+
test("proof-layer receipt verifies with standalone verifier", verify_receipt_standalone(proof_receipt)["valid"])
|
|
99
|
+
|
|
100
|
+
section("2. Governed Receipt Generation (Extension)")
|
|
101
|
+
governance_hash = hash_governance(**GOVERNANCE)
|
|
102
|
+
authorization_hash = hash_authorization(**AUTHORIZATION)
|
|
103
|
+
governed_receipt = generate_receipt(
|
|
104
|
+
intent_hash=intent_hash, execution_hash=execution_hash,
|
|
105
|
+
governance_hash=governance_hash, authorization_hash=authorization_hash,
|
|
106
|
+
intent_id=INTENT["intent_id"], action=INTENT["action"], agent_id=INTENT["agent_id"],
|
|
107
|
+
authorized_by="HUMAN:admin@example.com",
|
|
108
|
+
)
|
|
109
|
+
test("generates a governed receipt with 5-hash chain", governed_receipt["verification"]["chain_length"] == 5)
|
|
110
|
+
test("governed receipt chain_order has all 5 fields in order",
|
|
111
|
+
governed_receipt["verification"]["chain_order"] == [
|
|
112
|
+
"intent_hash", "governance_hash", "authorization_hash", "execution_hash", "receipt_hash"])
|
|
113
|
+
test("governed receipt self-verifies", verify_receipt(governed_receipt)["valid"])
|
|
114
|
+
test("governed receipt verifies with standalone verifier", verify_receipt_standalone(governed_receipt)["valid"])
|
|
115
|
+
|
|
116
|
+
section("3. Hash Integrity")
|
|
117
|
+
test("SHA-256 produces 64-char hex string", bool(HEX64.match(sha256("test"))))
|
|
118
|
+
test("SHA-256 is deterministic", sha256("hello") == sha256("hello"))
|
|
119
|
+
test("SHA-256 is collision-resistant (different inputs = different hashes)", sha256("input_a") != sha256("input_b"))
|
|
120
|
+
test("all receipt hashes are valid 64-char hex",
|
|
121
|
+
all(HEX64.match(v) for k, v in proof_receipt["hash_chain"].items() if v is not None))
|
|
122
|
+
tampered = copy.deepcopy(proof_receipt)
|
|
123
|
+
tampered["hash_chain"]["intent_hash"] = "a" * 64
|
|
124
|
+
test("tampered receipt fails verification", not verify_receipt(tampered)["valid"])
|
|
125
|
+
test("tampered receipt fails standalone verification", not verify_receipt_standalone(tampered)["valid"])
|
|
126
|
+
|
|
127
|
+
section("4. Ledger Operations")
|
|
128
|
+
ledger = create_ledger()
|
|
129
|
+
test("ledger starts empty with genesis hash",
|
|
130
|
+
ledger.get_entry_count() == 0 and ledger.get_current_hash() == GENESIS_HASH)
|
|
131
|
+
entry1 = ledger.append(
|
|
132
|
+
intent_id=INTENT["intent_id"], action=INTENT["action"], agent_id=INTENT["agent_id"],
|
|
133
|
+
status="executed", detail="Email sent successfully",
|
|
134
|
+
receipt_hash=proof_receipt["hash_chain"]["receipt_hash"],
|
|
135
|
+
)
|
|
136
|
+
test("append creates a valid entry with correct prev_hash",
|
|
137
|
+
entry1["prev_hash"] == GENESIS_HASH and bool(HEX64.match(entry1["ledger_hash"])))
|
|
138
|
+
entry2 = ledger.append(
|
|
139
|
+
intent_id="test-intent-002", action="schedule_meeting", agent_id=INTENT["agent_id"],
|
|
140
|
+
status="executed", detail="Meeting scheduled",
|
|
141
|
+
)
|
|
142
|
+
test("chain links correctly across multiple entries", entry2["prev_hash"] == entry1["ledger_hash"])
|
|
143
|
+
test("ledger chain verifies with standalone verifier", ledger.verify_chain()["valid"])
|
|
144
|
+
entries = ledger.export()
|
|
145
|
+
entries[0]["detail"] = "TAMPERED"
|
|
146
|
+
test("tampered ledger entry breaks chain verification", not verify_chain(entries)["valid"])
|
|
147
|
+
|
|
148
|
+
section("5. Cross-Verification (Receipt and Ledger)")
|
|
149
|
+
test("receipt cross-verifies against matching ledger entry",
|
|
150
|
+
verify_receipt_against_ledger(proof_receipt, entry1)["valid"])
|
|
151
|
+
test("mismatched receipt/ledger fails cross-verification",
|
|
152
|
+
not verify_receipt_against_ledger(proof_receipt, entry2)["valid"])
|
|
153
|
+
|
|
154
|
+
section("6. Batch Verification")
|
|
155
|
+
test("batch verifies multiple valid receipts",
|
|
156
|
+
verify_receipt_batch([proof_receipt, governed_receipt])["all_valid"])
|
|
157
|
+
tampered2 = copy.deepcopy(governed_receipt)
|
|
158
|
+
tampered2["hash_chain"]["receipt_hash"] = "a" * 64
|
|
159
|
+
batch_tampered = verify_receipt_batch([proof_receipt, tampered2])
|
|
160
|
+
test("batch detects tampered receipt among valid ones",
|
|
161
|
+
not batch_tampered["all_valid"] and batch_tampered["valid"] == 1)
|
|
162
|
+
|
|
163
|
+
section("7. Mixed Receipt Types (Proof-Layer + Governed)")
|
|
164
|
+
test("batch verifies mix of proof-layer and governed receipts",
|
|
165
|
+
verify_receipt_batch([proof_receipt, governed_receipt])["all_valid"])
|
|
166
|
+
mixed_ledger = create_ledger()
|
|
167
|
+
mixed_ledger.append(intent_id=INTENT["intent_id"], action=INTENT["action"],
|
|
168
|
+
agent_id=INTENT["agent_id"], status="executed",
|
|
169
|
+
detail="Proof-layer receipt", receipt_hash=proof_receipt["hash_chain"]["receipt_hash"])
|
|
170
|
+
mixed_ledger.append(intent_id=INTENT["intent_id"], action=INTENT["action"],
|
|
171
|
+
agent_id=INTENT["agent_id"], status="executed",
|
|
172
|
+
detail="Governed receipt", receipt_hash=governed_receipt["hash_chain"]["receipt_hash"])
|
|
173
|
+
test("ledger accepts both proof-layer and governed receipts", mixed_ledger.verify_chain()["valid"])
|
|
174
|
+
|
|
175
|
+
section("8. Optional Extensions")
|
|
176
|
+
ingestion_receipt = generate_receipt(
|
|
177
|
+
intent_hash=intent_hash, execution_hash=execution_hash,
|
|
178
|
+
intent_id=INTENT["intent_id"], action=INTENT["action"], agent_id=INTENT["agent_id"],
|
|
179
|
+
ingestion={"source": "api", "channel": "POST /intent", "source_message_id": "msg-123"},
|
|
180
|
+
)
|
|
181
|
+
test("receipt with ingestion provenance",
|
|
182
|
+
verify_receipt(ingestion_receipt)["valid"] and ingestion_receipt.get("ingestion") is not None)
|
|
183
|
+
identity_receipt = generate_receipt(
|
|
184
|
+
intent_hash=intent_hash, execution_hash=execution_hash,
|
|
185
|
+
intent_id=INTENT["intent_id"], action=INTENT["action"], agent_id=INTENT["agent_id"],
|
|
186
|
+
identity_binding={"signer_id": "human-root", "public_key_hex": "a" * 64,
|
|
187
|
+
"signature_payload_hash": "b" * 64, "verification_method": "ed25519-nacl",
|
|
188
|
+
"ed25519_signed": True},
|
|
189
|
+
)
|
|
190
|
+
test("receipt with identity binding",
|
|
191
|
+
verify_receipt(identity_receipt)["valid"] and identity_receipt.get("identity_binding") is not None)
|
|
192
|
+
plain_receipt = generate_receipt(
|
|
193
|
+
intent_hash=intent_hash, execution_hash=execution_hash,
|
|
194
|
+
intent_id=INTENT["intent_id"], action=INTENT["action"], agent_id=INTENT["agent_id"],
|
|
195
|
+
)
|
|
196
|
+
test("receipt without optional extensions still verifies", verify_receipt(plain_receipt)["valid"])
|
|
197
|
+
|
|
198
|
+
print()
|
|
199
|
+
print("=" * 60)
|
|
200
|
+
print("RIO Receipt Protocol v2.2 Python Conformance Results")
|
|
201
|
+
print("=" * 60)
|
|
202
|
+
print(f" Total: {total}")
|
|
203
|
+
print(f" Passed: {passed}")
|
|
204
|
+
if failed:
|
|
205
|
+
print(f" Failed: {failed}")
|
|
206
|
+
print("=" * 60)
|
|
207
|
+
if failed == 0:
|
|
208
|
+
print("\u2713 CONFORMANT \u2014 All tests passed")
|
|
209
|
+
else:
|
|
210
|
+
print("\u2717 NON-CONFORMANT \u2014 Some tests failed")
|
|
211
|
+
sys.exit(1)
|