Skip to content

Commit b7a9b86

Browse files
author
realdecimalist
committed
Add orynq-ai-auditability community ability
Creates tamper-proof, blockchain-anchored audit trails for AI conversations using Orynq's Proof-of-Inference protocol. - Builds SHA-256 rolling hash chains where each entry links to the previous one, making any tampering detectable - Uploads trace data to the Materios blob gateway (permissionless, no API key required) - The cert daemon committee (10 independent attestors) verifies data availability and certifies the receipt - Certified receipts are batched into Cardano mainnet anchor transactions (metadata label 8746) Trigger phrases: "audit my AI", "run orynq", "anchor this session", "proof of inference", "blockchain audit", etc. Docs: https://docs.fluxpointstudios.com/materios-partner-chain Explorer: https://materios.fluxpointstudios.com/explorer SDK: https://github.com/flux-point-studios/orynq-sdk
1 parent c65ca79 commit b7a9b86

3 files changed

Lines changed: 360 additions & 0 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Orynq AI Auditability
2+
3+
![Community](https://img.shields.io/badge/OpenHome-Community-orange?style=flat-square)
4+
![Author](https://img.shields.io/badge/Author-@flux--point--studios-lightgrey?style=flat-square)
5+
![Cardano](https://img.shields.io/badge/Blockchain-Cardano-blue?style=flat-square)
6+
7+
## What It Does
8+
9+
Creates **tamper-proof audit trails** for AI conversations. Each message is hashed into a rolling SHA-256 chain where modifying any entry invalidates all subsequent hashes — making tampering immediately detectable.
10+
11+
The hash chain is created locally with **zero setup required**. Optionally, it can be anchored to the **Materios partner chain** for permanent blockchain immutability, with certified receipts batched into Cardano mainnet transactions.
12+
13+
## Suggested Trigger Words
14+
15+
- "audit my AI"
16+
- "create audit trail"
17+
- "blockchain audit"
18+
- "proof of inference"
19+
- "verify AI"
20+
- "AI accountability"
21+
- "audit this conversation"
22+
- "run orynq"
23+
- "anchor this session"
24+
- "anchor this conversation"
25+
- "record AI decision"
26+
- "log this to blockchain"
27+
- "start audit"
28+
- "chain of custody"
29+
30+
## Setup
31+
32+
**No setup required for local audit trails.** Upload the ability and go.
33+
34+
For blockchain anchoring (optional):
35+
36+
| Path | Setup | Who pays fees |
37+
|------|-------|--------------|
38+
| **Sponsored** | Set `MATERIOS_GATEWAY_API_KEY` in `main.py` | FPS (included) |
39+
| **Permissionless** | Create a Materios wallet + get MATRA from faucet via [orynq-sdk](https://github.com/flux-point-studios/orynq-sdk) | You (free from faucet) |
40+
41+
## How It Works
42+
43+
1. Trigger with a phrase like "audit my AI" or "run orynq"
44+
2. Speak the messages you want in the audit trail
45+
3. Say "done" when finished
46+
4. The ability builds a SHA-256 rolling hash chain — **this is already tamper-proof locally**
47+
5. You're asked if you want to also anchor it to the blockchain
48+
6. If yes: the trace is uploaded to the Materios blob gateway, certified by 10 independent attestors, and batched into a Cardano mainnet anchor transaction
49+
50+
## Example Conversation
51+
52+
> **User:** "Audit this conversation"
53+
> **AI:** "I will create a tamper-proof audit trail. Tell me what to include."
54+
> **User:** "The model recommended treatment plan A for patient 42"
55+
> **AI:** "Got it. Say more to add entries, or say done when finished."
56+
> **User:** "Done"
57+
> **AI:** "Built a 1-entry hash chain. Anchor hash: 3f8a... This is already tamper-proof locally. Would you also like to anchor it to the blockchain?"
58+
> **User:** "Yes"
59+
> **AI:** "Your audit trail was uploaded to Materios. The committee will certify it and batch it into a Cardano mainnet transaction automatically."
60+
61+
## Why Auditability Matters
62+
63+
As AI systems make increasingly consequential decisions, organizations need provable records of what AI said and when. Traditional logging can be altered. Hash chain audit trails provide:
64+
65+
- **Tamper evidence** — Any modification breaks the chain
66+
- **Independent verification** — Anyone can recompute the hashes
67+
- **Blockchain immutability** — Optional on-chain anchoring via Cardano
68+
- **Regulatory compliance** — Immutable records for audit requirements
69+
70+
## Technical Details
71+
72+
- **Hash algorithm**: SHA-256 rolling chain (each entry includes the previous hash)
73+
- **Local artifact**: Always created, zero dependencies
74+
- **Blockchain**: Cardano mainnet via [Materios](https://docs.fluxpointstudios.com/materios-partner-chain) batched anchoring (metadata label `8746`)
75+
- **Committee**: 10 independent attestors verify data availability before certification
76+
- **Explorer**: [materios.fluxpointstudios.com/explorer](https://materios.fluxpointstudios.com/explorer/)
77+
- **SDK**: [orynq-sdk](https://github.com/flux-point-studios/orynq-sdk)

community/orynq-ai-auditability/__init__.py

Whitespace-only changes.
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import hashlib
2+
import json
3+
import time
4+
from typing import Optional
5+
6+
import requests
7+
from src.agent.capability import MatchingCapability
8+
from src.agent.capability_worker import CapabilityWorker
9+
from src.main import AgentWorker
10+
11+
# =============================================================================
12+
# Orynq AI Auditability Ability
13+
#
14+
# Creates tamper-proof audit trails for AI conversations using Orynq's
15+
# Proof-of-Inference protocol. Builds SHA-256 rolling hash chains that
16+
# are verifiable locally — no blockchain setup required.
17+
#
18+
# Optionally, the hash chain can be anchored to the Materios partner
19+
# chain and then batched into a Cardano mainnet transaction for
20+
# permanent on-chain immutability.
21+
#
22+
# Blockchain anchoring paths:
23+
# 1. Sponsored (API key) — FPS submits the receipt on your behalf
24+
# 2. Permissionless (own wallet) — you submit directly with MATRA
25+
# tokens from the faucet
26+
# =============================================================================
27+
28+
# --- Materios partner chain configuration ---
29+
MATERIOS_GATEWAY_URL = "https://materios.fluxpointstudios.com/blobs"
30+
MATERIOS_GATEWAY_API_KEY = "" # Optional — enables sponsored receipt submission
31+
32+
EXIT_WORDS = {"stop", "exit", "quit", "done", "cancel", "bye", "goodbye", "leave", "nothing"}
33+
YES_WORDS = {"yes", "yeah", "sure", "yep", "y", "ok", "okay", "anchor", "blockchain", "chain",
34+
"submit", "cardano", "materios", "on-chain", "onchain", "immutable"}
35+
36+
37+
class OrynqAiAuditabilityCapability(MatchingCapability):
38+
worker: AgentWorker = None
39+
capability_worker: CapabilityWorker = None
40+
41+
# Do not change following tag of register capability
42+
# {{register_capability}}
43+
44+
def call(self, worker: AgentWorker):
45+
self.worker = worker
46+
self.capability_worker = CapabilityWorker(self)
47+
self.worker.session_tasks.create(self.run())
48+
49+
def _log_info(self, msg: str):
50+
if self.worker:
51+
self.worker.editor_logging_handler.info(msg)
52+
53+
def _log_error(self, msg: str):
54+
if self.worker:
55+
self.worker.editor_logging_handler.error(msg)
56+
57+
def _is_exit(self, text: Optional[str]) -> bool:
58+
return (text or "").lower().strip() in EXIT_WORDS
59+
60+
def _build_hash_chain_entry(
61+
self,
62+
role: str,
63+
content: str,
64+
previous_hash: str,
65+
sequence: int,
66+
) -> dict:
67+
"""Build a single entry in the rolling SHA-256 hash chain."""
68+
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
69+
payload = json.dumps(
70+
{
71+
"seq": sequence,
72+
"role": role,
73+
"content": content,
74+
"prev": previous_hash,
75+
"ts": timestamp,
76+
},
77+
sort_keys=True,
78+
separators=(",", ":"),
79+
)
80+
entry_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
81+
return {
82+
"seq": sequence,
83+
"role": role,
84+
"content_hash": hashlib.sha256(content.encode("utf-8")).hexdigest(),
85+
"chain_hash": entry_hash,
86+
"previous_hash": previous_hash,
87+
"timestamp": timestamp,
88+
}
89+
90+
def _build_trace_content(self, trace: list[dict]) -> bytes:
91+
"""Serialize the trace into canonical JSON bytes for blob storage."""
92+
return json.dumps(trace, sort_keys=True, separators=(",", ":")).encode("utf-8")
93+
94+
def _compute_content_hash(self, content: bytes) -> str:
95+
"""SHA-256 hash of raw content bytes."""
96+
return hashlib.sha256(content).hexdigest()
97+
98+
# -----------------------------------------------------------------
99+
# Blockchain anchoring: upload to Materios gateway
100+
# -----------------------------------------------------------------
101+
def _anchor_to_materios(self, trace: list[dict]) -> Optional[dict]:
102+
"""Upload audit trail to Materios blob gateway.
103+
104+
With an API key: the gateway auto-submits a receipt on-chain
105+
(sponsored — no wallet needed).
106+
107+
Without an API key: the blob is stored on the gateway. The user
108+
can submit the receipt on-chain themselves using the orynq-sdk
109+
with a Materios wallet funded from the faucet.
110+
"""
111+
try:
112+
content = self._build_trace_content(trace)
113+
content_hash = self._compute_content_hash(content)
114+
115+
headers = {"Content-Type": "application/json"}
116+
if MATERIOS_GATEWAY_API_KEY:
117+
headers["x-api-key"] = MATERIOS_GATEWAY_API_KEY
118+
119+
# Step 1: Upload manifest
120+
manifest = {
121+
"chunks": [
122+
{"index": 0, "sha256": content_hash, "size": len(content)}
123+
],
124+
"total_size": len(content),
125+
}
126+
manifest_resp = requests.post(
127+
MATERIOS_GATEWAY_URL + "/" + content_hash + "/manifest",
128+
headers=headers,
129+
json=manifest,
130+
timeout=30,
131+
)
132+
if manifest_resp.status_code not in (200, 201, 409):
133+
self._log_error(
134+
"[OrynqAudit] Manifest upload failed: "
135+
+ str(manifest_resp.status_code)
136+
)
137+
return None
138+
self._log_info("[OrynqAudit] Manifest uploaded: " + content_hash[:16])
139+
140+
# Step 2: Upload chunk
141+
chunk_headers = {"Content-Type": "application/octet-stream"}
142+
if MATERIOS_GATEWAY_API_KEY:
143+
chunk_headers["x-api-key"] = MATERIOS_GATEWAY_API_KEY
144+
145+
chunk_resp = requests.put(
146+
MATERIOS_GATEWAY_URL + "/" + content_hash + "/chunks/0",
147+
headers=chunk_headers,
148+
data=content,
149+
timeout=30,
150+
)
151+
if chunk_resp.status_code not in (200, 201, 409):
152+
self._log_error(
153+
"[OrynqAudit] Chunk upload failed: "
154+
+ str(chunk_resp.status_code)
155+
)
156+
return None
157+
self._log_info("[OrynqAudit] Chunk uploaded: " + str(len(content)) + " bytes")
158+
159+
sponsored = bool(MATERIOS_GATEWAY_API_KEY)
160+
return {
161+
"content_hash": content_hash,
162+
"status": "submitted" if sponsored else "uploaded",
163+
"sponsored": sponsored,
164+
}
165+
166+
except Exception as e:
167+
self._log_error("[OrynqAudit] Materios error: " + str(e))
168+
return None
169+
170+
async def _collect_messages(self) -> list[tuple]:
171+
"""Collect user messages for the audit trail."""
172+
await self.capability_worker.speak(
173+
"Got it. I will include your messages in the audit trail. "
174+
"Say more to add entries, or say done when finished."
175+
)
176+
177+
first_input = await self.capability_worker.user_response()
178+
if not first_input or self._is_exit(first_input) or first_input.lower().strip() == "done":
179+
return []
180+
181+
messages = [("user", first_input)]
182+
183+
while True:
184+
next_input = await self.capability_worker.user_response()
185+
if not next_input or self._is_exit(next_input):
186+
break
187+
if next_input.lower().strip() == "done":
188+
break
189+
messages.append(("user", next_input))
190+
count = str(len(messages))
191+
await self.capability_worker.speak(
192+
"Added. " + count + " messages so far. Say done to finalize."
193+
)
194+
195+
return messages
196+
197+
async def run(self):
198+
try:
199+
await self.capability_worker.speak(
200+
"I will create a tamper-proof audit trail for this conversation. "
201+
"Each message gets hashed into a chain where any modification is detectable. "
202+
"Tell me what to include."
203+
)
204+
205+
messages_to_audit = await self._collect_messages()
206+
207+
if not messages_to_audit:
208+
await self.capability_worker.speak("No messages to audit. Cancelling.")
209+
return
210+
211+
# Build the rolling hash chain
212+
trace = []
213+
previous_hash = "0" * 64
214+
seq = 0
215+
for role, content in messages_to_audit:
216+
entry = self._build_hash_chain_entry(role, content, previous_hash, seq)
217+
trace.append(entry)
218+
previous_hash = entry["chain_hash"]
219+
seq += 1
220+
221+
trace_len = str(len(trace))
222+
anchor_hash = trace[-1]["chain_hash"]
223+
224+
# Local artifact is always created
225+
await self.capability_worker.speak(
226+
"Built a " + trace_len + "-entry hash chain. "
227+
"Anchor hash: " + anchor_hash[:16] + ". "
228+
"This is already tamper-proof locally. "
229+
"Would you also like to anchor it to the blockchain?"
230+
)
231+
232+
anchor_input = await self.capability_worker.user_response()
233+
wants_anchor = any(
234+
w in (anchor_input or "").lower().split()
235+
for w in YES_WORDS
236+
)
237+
238+
if not anchor_input or self._is_exit(anchor_input) or not wants_anchor:
239+
response_text = self.capability_worker.text_to_text_response(
240+
"Summarize for voice: a " + trace_len + "-entry tamper-proof hash chain "
241+
"was created locally. Anchor hash is " + anchor_hash[:16] + ". "
242+
"Not submitted to blockchain. One sentence."
243+
)
244+
await self.capability_worker.speak(response_text)
245+
return
246+
247+
# Anchor to Materios
248+
await self.capability_worker.speak("Uploading to Materios for blockchain anchoring.")
249+
result = self._anchor_to_materios(trace)
250+
251+
if result:
252+
ch = str(result.get("content_hash", ""))[:16]
253+
if result.get("sponsored"):
254+
response_text = self.capability_worker.text_to_text_response(
255+
"Summarize for voice: audit trail uploaded and receipt submitted "
256+
"to Materios. Content hash: " + ch + ". "
257+
"The committee will certify it and batch it into a Cardano "
258+
"mainnet transaction. Check materios.fluxpointstudios.com/explorer. "
259+
"One sentence."
260+
)
261+
else:
262+
response_text = self.capability_worker.text_to_text_response(
263+
"Summarize for voice: audit trail uploaded to Materios gateway. "
264+
"Content hash: " + ch + ". To complete on-chain submission, "
265+
"use the orynq SDK with a Materios wallet funded from the faucet. "
266+
"One sentence."
267+
)
268+
await self.capability_worker.speak(response_text)
269+
else:
270+
await self.capability_worker.speak(
271+
"Could not reach the gateway right now. "
272+
"Your local hash chain is still valid. Anchor hash: " + anchor_hash[:16]
273+
)
274+
275+
except Exception as e:
276+
self._log_error("[OrynqAudit] Unexpected error: " + str(e))
277+
if self.capability_worker:
278+
await self.capability_worker.speak(
279+
"Sorry, something went wrong. Please try again."
280+
)
281+
finally:
282+
if self.capability_worker:
283+
self.capability_worker.resume_normal_flow()

0 commit comments

Comments
 (0)