vouch-protocol 1.0.0__py3-none-any.whl
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.
- vouch/__init__.py +3 -0
- vouch/auditor.py +28 -0
- vouch/integrations/autogen/__init__.py +0 -0
- vouch/integrations/autogen/tool.py +22 -0
- vouch/integrations/autogpt/__init__.py +0 -0
- vouch/integrations/autogpt/commands.py +30 -0
- vouch/integrations/crewai/__init__.py +0 -0
- vouch/integrations/crewai/tool.py +16 -0
- vouch/integrations/langchain/__init__.py +0 -0
- vouch/integrations/langchain/tool.py +25 -0
- vouch/integrations/langchain/tools.py +49 -0
- vouch/integrations/mcp/__init__.py +0 -0
- vouch/integrations/mcp/server.py +71 -0
- vouch/integrations/vertex_ai/__init__.py +0 -0
- vouch/integrations/vertex_ai/tool.py +9 -0
- vouch/keys.py +24 -0
- vouch/seal.py +7 -0
- vouch/verifier.py +57 -0
- vouch_protocol-1.0.0.dist-info/METADATA +891 -0
- vouch_protocol-1.0.0.dist-info/RECORD +23 -0
- vouch_protocol-1.0.0.dist-info/WHEEL +5 -0
- vouch_protocol-1.0.0.dist-info/licenses/LICENSE +661 -0
- vouch_protocol-1.0.0.dist-info/top_level.txt +1 -0
vouch/__init__.py
ADDED
vouch/auditor.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
import uuid
|
|
4
|
+
from jwcrypto import jwk, jws
|
|
5
|
+
|
|
6
|
+
class Auditor:
|
|
7
|
+
def __init__(self, private_key_json):
|
|
8
|
+
self.signing_key = jwk.JWK.from_json(private_key_json)
|
|
9
|
+
|
|
10
|
+
def issue_vouch(self, agent_data):
|
|
11
|
+
# Default starting score for new agents
|
|
12
|
+
reputation_score = agent_data.get('reputation_score', 50)
|
|
13
|
+
|
|
14
|
+
payload = {
|
|
15
|
+
"jti": str(uuid.uuid4()),
|
|
16
|
+
"sub": agent_data.get('did'),
|
|
17
|
+
"iss": "did:web:vouch-authority",
|
|
18
|
+
"nbf": int(time.time()),
|
|
19
|
+
"exp": int(time.time()) + 86400, # 24 Hours
|
|
20
|
+
"vc": {
|
|
21
|
+
"integrity_hash": agent_data.get('integrity_hash'),
|
|
22
|
+
"reputation_score": reputation_score, # <-- NEW MANIFESTO FIELD
|
|
23
|
+
"type": "Identity+Reputation"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
signer = jws.JWS(json.dumps(payload).encode('utf-8'))
|
|
27
|
+
signer.add_signature(self.signing_key, None, json.dumps({"alg":"EdDSA"}), json.dumps({"kid":self.signing_key.key_id}))
|
|
28
|
+
return {"certificate": signer.serialize(compact=True)}
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from vouch import Auditor
|
|
2
|
+
import os
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
def sign_action(
|
|
6
|
+
intent: Annotated[str, "The action you are about to take"]
|
|
7
|
+
) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Generates a Vouch-Token to sign a sensitive action for AutoGen agents.
|
|
10
|
+
"""
|
|
11
|
+
key = os.getenv("VOUCH_PRIVATE_KEY")
|
|
12
|
+
did = os.getenv("VOUCH_DID")
|
|
13
|
+
|
|
14
|
+
if not key:
|
|
15
|
+
return "Error: VOUCH_PRIVATE_KEY not configured."
|
|
16
|
+
|
|
17
|
+
auditor = Auditor(key)
|
|
18
|
+
proof = auditor.issue_vouch({
|
|
19
|
+
"did": did,
|
|
20
|
+
"integrity_hash": intent
|
|
21
|
+
})
|
|
22
|
+
return f"Vouch-Token: {proof['certificate']}"
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from vouch import Auditor
|
|
3
|
+
|
|
4
|
+
# Mock decorator if AutoGPT is not installed
|
|
5
|
+
try:
|
|
6
|
+
from autogpt.command_decorator import command
|
|
7
|
+
except ImportError:
|
|
8
|
+
def command(*args, **kwargs):
|
|
9
|
+
def decorator(func): return func
|
|
10
|
+
return decorator
|
|
11
|
+
|
|
12
|
+
@command(
|
|
13
|
+
"sign_with_vouch",
|
|
14
|
+
"Generates a Vouch-Token to prove identity",
|
|
15
|
+
{
|
|
16
|
+
"intent": {"type": "string", "description": "What you are doing", "required": True},
|
|
17
|
+
"target_service": {"type": "string", "description": "Target domain", "required": True}
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
def sign_with_vouch(intent: str, target_service: str) -> str:
|
|
21
|
+
try:
|
|
22
|
+
key = os.getenv("VOUCH_PRIVATE_KEY")
|
|
23
|
+
did = os.getenv("VOUCH_DID", "did:web:anonymous")
|
|
24
|
+
if not key: return "Error: VOUCH_PRIVATE_KEY missing"
|
|
25
|
+
|
|
26
|
+
auditor = Auditor(key)
|
|
27
|
+
proof = auditor.issue_vouch({"did": did, "integrity_hash": f"{target_service}:{intent}"})
|
|
28
|
+
return f"Vouch-Token generated. Add header: 'Vouch-Token: {proof['certificate']}'"
|
|
29
|
+
except Exception as e:
|
|
30
|
+
return f"Error: {str(e)}"
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from langchain.tools import tool
|
|
2
|
+
from vouch import Auditor
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class VouchCrewTools:
|
|
6
|
+
@tool("Sign Request with Vouch")
|
|
7
|
+
def sign_request(intent: str):
|
|
8
|
+
"""Generates a cryptographic Vouch-Token. Useful for proving identity to external APIs."""
|
|
9
|
+
key = os.getenv("VOUCH_PRIVATE_KEY")
|
|
10
|
+
did = os.getenv("VOUCH_DID")
|
|
11
|
+
|
|
12
|
+
if not key: return "Error: VOUCH_PRIVATE_KEY not found."
|
|
13
|
+
|
|
14
|
+
auditor = Auditor(key)
|
|
15
|
+
proof = auditor.issue_vouch({"did": did, "integrity_hash": intent})
|
|
16
|
+
return f"Vouch-Token: {proof['certificate']}"
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Optional, Type
|
|
2
|
+
from langchain.tools import BaseTool
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from vouch import Auditor
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
class VouchSignerInput(BaseModel):
|
|
8
|
+
intent: str = Field(description="A description of the action being signed (e.g. 'search_database')")
|
|
9
|
+
|
|
10
|
+
class VouchSignerTool(BaseTool):
|
|
11
|
+
name = "vouch_signer"
|
|
12
|
+
description = "Generates a verifiable identity proof (Vouch-Token) for sensitive actions."
|
|
13
|
+
args_schema: Type[BaseModel] = VouchSignerInput
|
|
14
|
+
|
|
15
|
+
def _run(self, intent: str) -> str:
|
|
16
|
+
private_key = os.getenv("VOUCH_PRIVATE_KEY")
|
|
17
|
+
agent_did = os.getenv("VOUCH_DID")
|
|
18
|
+
if not private_key: return "Error: VOUCH_PRIVATE_KEY not set."
|
|
19
|
+
|
|
20
|
+
auditor = Auditor(private_key)
|
|
21
|
+
proof = auditor.issue_vouch({"did": agent_did, "integrity_hash": intent})
|
|
22
|
+
return f"Vouch-Token: {proof['certificate']}"
|
|
23
|
+
|
|
24
|
+
def _arun(self, intent: str):
|
|
25
|
+
raise NotImplementedError("Async not implemented yet")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Optional, Type
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
# Try importing LangChain (It's optional, so we handle the error if missing)
|
|
5
|
+
try:
|
|
6
|
+
from langchain.tools import BaseTool
|
|
7
|
+
except ImportError:
|
|
8
|
+
# Fallback for users who don't have LangChain installed
|
|
9
|
+
class BaseTool:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
from vouch import Auditor
|
|
13
|
+
|
|
14
|
+
class VouchSignerInput(BaseModel):
|
|
15
|
+
"""Input for the Vouch Signer."""
|
|
16
|
+
action_summary: str = Field(..., description="A brief summary of what action the agent is taking (e.g., 'booking_flight')")
|
|
17
|
+
|
|
18
|
+
class VouchSignerTool(BaseTool):
|
|
19
|
+
name = "vouch_identity_signer"
|
|
20
|
+
description = "Use this tool to generate a cryptographic Vouch-Token when you need to prove your identity to an external API."
|
|
21
|
+
args_schema: Type[BaseModel] = VouchSignerInput
|
|
22
|
+
|
|
23
|
+
# Private attributes
|
|
24
|
+
_auditor: Auditor = None
|
|
25
|
+
_did: str = None
|
|
26
|
+
|
|
27
|
+
def __init__(self, private_key_json: str, agent_did: str):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self._auditor = Auditor(private_key_json)
|
|
30
|
+
self._did = agent_did
|
|
31
|
+
|
|
32
|
+
def _run(self, action_summary: str) -> str:
|
|
33
|
+
"""Generates the Vouch-Token header."""
|
|
34
|
+
|
|
35
|
+
# Create the payload based on the agent's intent
|
|
36
|
+
vouch_data = {
|
|
37
|
+
"did": self._did,
|
|
38
|
+
"integrity_hash": f"action:{action_summary}" # Simple binding of intent
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Issue the token
|
|
42
|
+
result = self._auditor.issue_vouch(vouch_data)
|
|
43
|
+
|
|
44
|
+
# Return the ready-to-use header value
|
|
45
|
+
return result['certificate']
|
|
46
|
+
|
|
47
|
+
async def _arun(self, action_summary: str) -> str:
|
|
48
|
+
"""Async support (optional but good practice)."""
|
|
49
|
+
return self._run(action_summary)
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from vouch import Auditor
|
|
5
|
+
|
|
6
|
+
# A simple Standard-IO Model Context Protocol (MCP) Server
|
|
7
|
+
# This allows Claude Desktop or Cursor to "use" Vouch tools locally.
|
|
8
|
+
|
|
9
|
+
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
|
|
10
|
+
|
|
11
|
+
class VouchMCPServer:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.auditor = None
|
|
14
|
+
# Try to load keys from env
|
|
15
|
+
self.load_keys()
|
|
16
|
+
|
|
17
|
+
def load_keys(self):
|
|
18
|
+
# In a real app, manage this securely. For MVP, we look for env vars.
|
|
19
|
+
import os
|
|
20
|
+
key = os.getenv("VOUCH_PRIVATE_KEY")
|
|
21
|
+
if key:
|
|
22
|
+
self.auditor = Auditor(key)
|
|
23
|
+
|
|
24
|
+
def process_request(self, line):
|
|
25
|
+
try:
|
|
26
|
+
request = json.loads(line)
|
|
27
|
+
if request.get("method") == "tools/list":
|
|
28
|
+
return {
|
|
29
|
+
"jsonrpc": "2.0",
|
|
30
|
+
"id": request["id"],
|
|
31
|
+
"result": {
|
|
32
|
+
"tools": [{
|
|
33
|
+
"name": "sign_action",
|
|
34
|
+
"description": "Generate a cryptographic Vouch-Token to sign a sensitive action.",
|
|
35
|
+
"inputSchema": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"intent": {"type": "string", "description": "What are you doing? (e.g. 'read_email')"},
|
|
39
|
+
"did": {"type": "string", "description": "Your Agent DID"}
|
|
40
|
+
},
|
|
41
|
+
"required": ["intent", "did"]
|
|
42
|
+
}
|
|
43
|
+
}]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
elif request.get("method") == "tools/call":
|
|
47
|
+
if request["params"]["name"] == "sign_action":
|
|
48
|
+
if not self.auditor:
|
|
49
|
+
return {"jsonrpc": "2.0", "id": request["id"], "error": {"code": -32603, "message": "VOUCH_PRIVATE_KEY not set in env"}}
|
|
50
|
+
|
|
51
|
+
args = request["params"]["arguments"]
|
|
52
|
+
proof = self.auditor.issue_vouch({
|
|
53
|
+
"did": args["did"],
|
|
54
|
+
"integrity_hash": args["intent"]
|
|
55
|
+
})
|
|
56
|
+
return {
|
|
57
|
+
"jsonrpc": "2.0", "id": request["id"],
|
|
58
|
+
"result": {"content": [{"type": "text", "text": proof["certificate"]}]}
|
|
59
|
+
}
|
|
60
|
+
except Exception as e:
|
|
61
|
+
pass
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
server = VouchMCPServer()
|
|
66
|
+
# MCP loop
|
|
67
|
+
for line in sys.stdin:
|
|
68
|
+
response = server.process_request(line)
|
|
69
|
+
if response:
|
|
70
|
+
print(json.dumps(response))
|
|
71
|
+
sys.stdout.flush()
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from vouch import Auditor
|
|
2
|
+
import os
|
|
3
|
+
def sign_request_with_vouch(intent: str) -> str:
|
|
4
|
+
key = os.environ.get("VOUCH_PRIVATE_KEY")
|
|
5
|
+
did = os.environ.get("VOUCH_DID")
|
|
6
|
+
if not key: return "Error: Keys missing"
|
|
7
|
+
auditor = Auditor(key)
|
|
8
|
+
proof = auditor.issue_vouch({"did": did, "integrity_hash": intent})
|
|
9
|
+
return f"Vouch-Token: {proof['certificate']}"
|
vouch/keys.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from jwcrypto import jwk
|
|
3
|
+
|
|
4
|
+
def generate_identity():
|
|
5
|
+
"""
|
|
6
|
+
Generates a fresh Ed25519 Keypair for a new Agent.
|
|
7
|
+
"""
|
|
8
|
+
# 1. Generate Key
|
|
9
|
+
key = jwk.JWK.generate(kty='OKP', crv='Ed25519')
|
|
10
|
+
|
|
11
|
+
# 2. Export Private Key (Save this securely!)
|
|
12
|
+
private_key = key.export_private()
|
|
13
|
+
|
|
14
|
+
# 3. Export Public Key (Put this in vouch.json)
|
|
15
|
+
public_key = key.export_public()
|
|
16
|
+
|
|
17
|
+
print("🔑 NEW AGENT IDENTITY GENERATED\n")
|
|
18
|
+
print("--- PRIVATE KEY (Keep Secret / Set as Env Var) ---")
|
|
19
|
+
print(private_key)
|
|
20
|
+
print("\n--- PUBLIC KEY (Put this in vouch.json) ---")
|
|
21
|
+
print(public_key)
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
generate_identity()
|
vouch/seal.py
ADDED
vouch/verifier.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
import base64
|
|
4
|
+
import requests
|
|
5
|
+
from jwcrypto import jwk, jws
|
|
6
|
+
|
|
7
|
+
class Verifier:
|
|
8
|
+
def __init__(self, trusted_roots=None):
|
|
9
|
+
self.trusted_roots = trusted_roots or {}
|
|
10
|
+
self.used_nonces = set()
|
|
11
|
+
|
|
12
|
+
def _resolve_did(self, did):
|
|
13
|
+
if did in self.trusted_roots:
|
|
14
|
+
return jwk.JWK.from_json(self.trusted_roots[did])
|
|
15
|
+
|
|
16
|
+
if not did.startswith("did:web:"):
|
|
17
|
+
raise ValueError(f"Unsupported DID method: {did}")
|
|
18
|
+
|
|
19
|
+
domain = did.replace("did:web:", "")
|
|
20
|
+
url = f"https://{domain}/.well-known/did.json"
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
response = requests.get(url, timeout=5)
|
|
24
|
+
response.raise_for_status()
|
|
25
|
+
did_doc = response.json()
|
|
26
|
+
key_data = did_doc['verificationMethod'][0]['publicKeyJwk']
|
|
27
|
+
return jwk.JWK.from_json(json.dumps(key_data))
|
|
28
|
+
except Exception as e:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
def check_vouch(self, token):
|
|
32
|
+
try:
|
|
33
|
+
parts = token.split('.')
|
|
34
|
+
if len(parts) != 3: return False, "Invalid Token Format"
|
|
35
|
+
|
|
36
|
+
payload_str = parts[1] + '=' * (-len(parts[1]) % 4)
|
|
37
|
+
payload = json.loads(base64.urlsafe_b64decode(payload_str))
|
|
38
|
+
|
|
39
|
+
agent_did = payload.get('sub')
|
|
40
|
+
if not agent_did: return False, "No Identity (sub) in token"
|
|
41
|
+
|
|
42
|
+
public_key = self._resolve_did(agent_did)
|
|
43
|
+
if not public_key:
|
|
44
|
+
return False, f"Could not resolve public key for {agent_did}"
|
|
45
|
+
|
|
46
|
+
verifier = jws.JWS()
|
|
47
|
+
verifier.deserialize(token)
|
|
48
|
+
verifier.verify(public_key)
|
|
49
|
+
|
|
50
|
+
if time.time() > payload['exp']: return False, "Expired Vouch"
|
|
51
|
+
if payload['jti'] in self.used_nonces: return False, "Replay Detected"
|
|
52
|
+
self.used_nonces.add(payload['jti'])
|
|
53
|
+
|
|
54
|
+
return True, payload
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return False, f"Verification Error: {str(e)}"
|