Skip to content

Commit fa50a7a

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 fa50a7a

3 files changed

Lines changed: 338 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, blockchain-anchored audit trails for AI conversations using Orynq's Proof-of-Inference protocol. Each message is hashed into a rolling SHA-256 chain and uploaded to the **Materios partner chain**, where a decentralized committee certifies data availability and batches certified receipts into Cardano mainnet anchor transactions.
10+
11+
**No API key or wallet required.** Materios receipt submission is permissionless — the blob gateway accepts uploads from any client.
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+
1. Upload the ability to your OpenHome dashboard — it works out of the box
33+
2. (Optional) Set `MATERIOS_GATEWAY_API_KEY` in `main.py` for higher rate limits
34+
35+
No API key is required for basic operation. The Materios blob gateway accepts uploads from any client.
36+
37+
## How It Works
38+
39+
1. You trigger the ability with a phrase like "audit my AI" or "run orynq"
40+
2. Speak the messages you want included in the audit trail
41+
3. Say "done" when finished
42+
4. The ability builds a SHA-256 rolling hash chain where each entry links to the previous one
43+
5. The trace is uploaded to the Materios blob gateway (manifest + chunk)
44+
6. The cert daemon committee (10 independent attestors) verifies the blob
45+
7. Once certified, the receipt is batched into a Cardano mainnet anchor transaction (label `8746`)
46+
47+
The hash chain is tamper-proof: changing any single entry invalidates all subsequent hashes, making unauthorized modifications detectable.
48+
49+
## Example Conversation
50+
51+
> **User:** "Audit this conversation"
52+
> **AI:** "I will create a blockchain audit trail for this conversation. Tell me what to include."
53+
> **User:** "The agent processed 500 claims with a 98.2% accuracy rate"
54+
> **AI:** "Got it. Say more to add entries, or say done when finished."
55+
> **User:** "Done"
56+
> **AI:** "Your 1-entry audit trail was uploaded to Materios. The cert daemon committee will verify it, then it gets batched into a Cardano mainnet anchor automatically."
57+
58+
You can check the status at [materios.fluxpointstudios.com/explorer](https://materios.fluxpointstudios.com/explorer/).
59+
60+
## Why Auditability Matters
61+
62+
As AI systems make increasingly consequential decisions, organizations need provable records of what AI said and when. Traditional logging can be altered. Blockchain anchoring provides:
63+
64+
- **Tamper evidence** - Any modification breaks the hash chain
65+
- **Independent verification** - Anyone can verify the trail on-chain
66+
- **Regulatory compliance** - Immutable records for audit requirements
67+
- **Accountability** - Provable AI decision history
68+
69+
## Technical Details
70+
71+
- **Hash algorithm**: SHA-256 rolling chain
72+
- **Blockchain**: Cardano mainnet (via Materios batched anchoring, metadata label `8746`)
73+
- **Partner chain**: [Materios](https://docs.fluxpointstudios.com/materios-partner-chain) — Substrate chain with 10-member attestation committee
74+
- **Auth**: Permissionless (optional API key for higher rate limits)
75+
- **Gateway**: `materios.fluxpointstudios.com/blobs` — blob storage + manifest API
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: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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, blockchain-anchored audit trails for AI conversations
15+
# using Orynq's Proof-of-Inference protocol. Builds SHA-256 rolling hash
16+
# chains and anchors them to the Materios partner chain.
17+
#
18+
# Materios receipt submission is PERMISSIONLESS — no API key required.
19+
# The blob gateway accepts sr25519-signed uploads, and MATRA tokens for
20+
# TX fees are available from the on-chain faucet.
21+
#
22+
# An optional API key provides higher gateway rate limits but is not
23+
# required for any operation.
24+
# =============================================================================
25+
26+
# --- Materios partner chain configuration ---
27+
MATERIOS_GATEWAY_URL = "https://materios.fluxpointstudios.com/blobs"
28+
MATERIOS_GATEWAY_API_KEY = "" # Optional — sr25519 auth works without it
29+
30+
EXIT_WORDS = {"stop", "exit", "quit", "done", "cancel", "bye", "goodbye", "leave", "nothing"}
31+
32+
33+
class OrynqAiAuditabilityCapability(MatchingCapability):
34+
worker: AgentWorker = None
35+
capability_worker: CapabilityWorker = None
36+
37+
# Do not change following tag of register capability
38+
# {{register_capability}}
39+
40+
def call(self, worker: AgentWorker):
41+
self.worker = worker
42+
self.capability_worker = CapabilityWorker(self)
43+
self.worker.session_tasks.create(self.run())
44+
45+
def _log_info(self, msg: str):
46+
if self.worker:
47+
self.worker.editor_logging_handler.info(msg)
48+
49+
def _log_error(self, msg: str):
50+
if self.worker:
51+
self.worker.editor_logging_handler.error(msg)
52+
53+
def _is_exit(self, text: Optional[str]) -> bool:
54+
return (text or "").lower().strip() in EXIT_WORDS
55+
56+
def _build_hash_chain_entry(
57+
self,
58+
role: str,
59+
content: str,
60+
previous_hash: str,
61+
sequence: int,
62+
) -> dict:
63+
"""Build a single entry in the rolling SHA-256 hash chain."""
64+
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
65+
payload = json.dumps(
66+
{
67+
"seq": sequence,
68+
"role": role,
69+
"content": content,
70+
"prev": previous_hash,
71+
"ts": timestamp,
72+
},
73+
sort_keys=True,
74+
separators=(",", ":"),
75+
)
76+
entry_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()
77+
return {
78+
"seq": sequence,
79+
"role": role,
80+
"content_hash": hashlib.sha256(content.encode("utf-8")).hexdigest(),
81+
"chain_hash": entry_hash,
82+
"previous_hash": previous_hash,
83+
"timestamp": timestamp,
84+
}
85+
86+
def _build_trace_content(self, trace: list[dict]) -> bytes:
87+
"""Serialize the trace into canonical JSON bytes for blob storage."""
88+
return json.dumps(trace, sort_keys=True, separators=(",", ":")).encode("utf-8")
89+
90+
def _compute_content_hash(self, content: bytes) -> str:
91+
"""SHA-256 hash of raw content bytes."""
92+
return hashlib.sha256(content).hexdigest()
93+
94+
# -----------------------------------------------------------------
95+
# Materios submission: blob upload -> receipt (permissionless)
96+
# -----------------------------------------------------------------
97+
def _submit_to_materios(self, trace: list[dict]) -> Optional[dict]:
98+
"""Submit audit trail to Materios blob gateway.
99+
100+
Flow: upload manifest + chunk to gateway -> the cert daemon committee
101+
discovers the blob, verifies integrity, and attests availability.
102+
Certified receipts are batched into Cardano L1 anchor transactions.
103+
104+
No API key required — the gateway accepts uploads from any client.
105+
An optional API key provides higher rate limits.
106+
"""
107+
try:
108+
content = self._build_trace_content(trace)
109+
content_hash = self._compute_content_hash(content)
110+
chunk_hash = self._compute_content_hash(content)
111+
112+
headers = {"Content-Type": "application/json"}
113+
if MATERIOS_GATEWAY_API_KEY:
114+
headers["x-api-key"] = MATERIOS_GATEWAY_API_KEY
115+
116+
# Step 1: Upload manifest (declares the blob structure)
117+
manifest = {
118+
"chunks": [
119+
{"index": 0, "sha256": chunk_hash, "size": len(content)}
120+
],
121+
"total_size": len(content),
122+
}
123+
manifest_resp = requests.post(
124+
MATERIOS_GATEWAY_URL + "/" + content_hash + "/manifest",
125+
headers=headers,
126+
json=manifest,
127+
timeout=30,
128+
)
129+
if manifest_resp.status_code not in (200, 201, 409):
130+
self._log_error(
131+
"[OrynqAudit] Manifest upload failed: "
132+
+ str(manifest_resp.status_code) + " " + manifest_resp.text[:200]
133+
)
134+
return None
135+
self._log_info("[OrynqAudit] Manifest uploaded: " + content_hash[:16] + "...")
136+
137+
# Step 2: Upload chunk data (the actual trace content)
138+
chunk_headers = {"Content-Type": "application/octet-stream"}
139+
if MATERIOS_GATEWAY_API_KEY:
140+
chunk_headers["x-api-key"] = MATERIOS_GATEWAY_API_KEY
141+
142+
chunk_resp = requests.put(
143+
MATERIOS_GATEWAY_URL + "/" + content_hash + "/chunks/0",
144+
headers=chunk_headers,
145+
data=content,
146+
timeout=30,
147+
)
148+
if chunk_resp.status_code not in (200, 201, 409):
149+
self._log_error(
150+
"[OrynqAudit] Chunk upload failed: "
151+
+ str(chunk_resp.status_code) + " " + chunk_resp.text[:200]
152+
)
153+
return None
154+
self._log_info("[OrynqAudit] Chunk uploaded: " + str(len(content)) + " bytes")
155+
156+
return {
157+
"path": "materios",
158+
"content_hash": content_hash,
159+
"status": "uploaded",
160+
"gateway": MATERIOS_GATEWAY_URL,
161+
"entry_count": len(trace),
162+
"anchor_hash": trace[-1]["chain_hash"] if trace else "",
163+
}
164+
165+
except Exception as e:
166+
self._log_error("[OrynqAudit] Materios submission error: " + str(e))
167+
return None
168+
169+
async def _collect_messages(self) -> list[tuple]:
170+
"""Collect user messages for the audit trail."""
171+
await self.capability_worker.speak(
172+
"Got it. I will include your messages in the audit trail. "
173+
"Say more to add entries, or say done when finished."
174+
)
175+
176+
first_input = await self.capability_worker.user_response()
177+
if not first_input or self._is_exit(first_input) or first_input.lower().strip() == "done":
178+
return []
179+
180+
messages = [("user", first_input)]
181+
182+
while True:
183+
next_input = await self.capability_worker.user_response()
184+
if not next_input or self._is_exit(next_input):
185+
break
186+
if next_input.lower().strip() == "done":
187+
break
188+
messages.append(("user", next_input))
189+
count = str(len(messages))
190+
await self.capability_worker.speak(
191+
"Added. " + count + " messages in the trail so far. Say done to finalize."
192+
)
193+
194+
return messages
195+
196+
async def _speak_result(self, result: Optional[dict], trace: list[dict]):
197+
"""Speak the anchoring result to the user."""
198+
trace_len = str(len(trace))
199+
200+
if not result:
201+
anchor_hash = trace[-1]["chain_hash"]
202+
await self.capability_worker.speak(
203+
"I built a " + trace_len + "-entry hash chain but could not upload to the gateway. "
204+
"Your local anchor hash is " + anchor_hash[:16] + ". You can retry later."
205+
)
206+
return
207+
208+
content_hash = str(result.get("content_hash", ""))[:16]
209+
response_text = self.capability_worker.text_to_text_response(
210+
"Summarize for voice: an audit trail with " + trace_len + " entries was uploaded "
211+
"to the Materios blob gateway. Content hash is " + content_hash + ". "
212+
"The cert daemon committee will verify the blob, then it gets certified and "
213+
"batched into a Cardano mainnet anchor transaction automatically. "
214+
"You can check status at materios.fluxpointstudios.com/explorer. One sentence."
215+
)
216+
await self.capability_worker.speak(response_text)
217+
218+
async def run(self):
219+
try:
220+
await self.capability_worker.speak(
221+
"I will create a blockchain audit trail for this conversation. "
222+
"Your messages will be hashed into a tamper-proof chain and "
223+
"anchored to the Materios partner chain, which certifies it and "
224+
"batches it into a Cardano mainnet transaction. "
225+
"Tell me what to include in the audit trail."
226+
)
227+
228+
messages_to_audit = await self._collect_messages()
229+
230+
if not messages_to_audit:
231+
await self.capability_worker.speak("No messages to audit. Cancelling.")
232+
return
233+
234+
# Build the rolling hash chain
235+
trace = []
236+
previous_hash = "0" * 64 # genesis hash
237+
seq = 0
238+
for role, content in messages_to_audit:
239+
entry = self._build_hash_chain_entry(role, content, previous_hash, seq)
240+
trace.append(entry)
241+
previous_hash = entry["chain_hash"]
242+
seq += 1
243+
244+
trace_len = str(len(trace))
245+
await self.capability_worker.speak(
246+
"Built a " + trace_len + "-entry hash chain. "
247+
"Uploading to Materios for certified anchoring."
248+
)
249+
250+
result = self._submit_to_materios(trace)
251+
await self._speak_result(result, trace)
252+
253+
except Exception as e:
254+
self._log_error("[OrynqAudit] Unexpected error: " + str(e))
255+
if self.capability_worker:
256+
await self.capability_worker.speak(
257+
"Sorry, something went wrong creating the audit trail. Please try again."
258+
)
259+
finally:
260+
if self.capability_worker:
261+
self.capability_worker.resume_normal_flow()

0 commit comments

Comments
 (0)