/ examples / agent / langchain_tool.py
langchain_tool.py
  1  """
  2  AI Agent Signed Tool Call Example
  3  =================================
  4  
  5  Demonstrates how an AI agent signs tool calls with Auths action envelopes,
  6  and how a server verifies them before execution.
  7  
  8  Requirements:
  9      pip install auths-verifier
 10  
 11  Security Note:
 12      This example passes key material as hex strings for simplicity. Python
 13      strings are immutable and not zeroizable. For production, store keys in
 14      a secure enclave, hardware security module, or secret manager (e.g.,
 15      AWS KMS, HashiCorp Vault, macOS Keychain).
 16  
 17  Usage:
 18      python langchain_tool.py
 19  """
 20  
 21  import json
 22  import sys
 23  
 24  try:
 25      from auths_verifier import sign_action, verify_action_envelope
 26  except ImportError:
 27      print("Install auths-verifier: pip install auths-verifier")
 28      sys.exit(1)
 29  
 30  
 31  def generate_test_keypair():
 32      """Generate an Ed25519 keypair for demonstration.
 33  
 34      In production, use `auths id create` to generate keys stored in the
 35      system keychain, then export the seed for agent use.
 36      """
 37      try:
 38          from cryptography.hazmat.primitives.asymmetric.ed25519 import (
 39              Ed25519PrivateKey,
 40          )
 41  
 42          private_key = Ed25519PrivateKey.generate()
 43          seed_hex = private_key.private_bytes_raw().hex()
 44          pub_hex = private_key.public_key().public_bytes_raw().hex()
 45          return seed_hex, pub_hex
 46      except ImportError:
 47          print("Install cryptography for key generation: pip install cryptography")
 48          print("(or use a pre-generated key from `auths id create`)")
 49          sys.exit(1)
 50  
 51  
 52  # --- Agent Side ---
 53  
 54  
 55  def agent_execute_tool(agent_seed_hex: str, agent_did: str, tool_name: str, args: dict) -> str:
 56      """Agent signs a tool call and returns the signed envelope.
 57  
 58      This would typically be called by a LangChain tool wrapper or similar
 59      agent framework before sending the request to a server.
 60      """
 61      payload = {"tool": tool_name, "args": args}
 62  
 63      envelope_json = sign_action(
 64          private_key_hex=agent_seed_hex,
 65          action_type="tool_call",
 66          payload_json=json.dumps(payload),
 67          identity_did=agent_did,
 68      )
 69  
 70      return envelope_json
 71  
 72  
 73  # --- Server Side ---
 74  
 75  
 76  def server_verify_and_execute(envelope_json: str, trusted_keys: dict) -> dict:
 77      """Server verifies the signed envelope and executes the tool call.
 78  
 79      Args:
 80          envelope_json: The signed action envelope from the agent
 81          trusted_keys: Mapping of DID -> public key hex for authorized agents
 82  
 83      Returns:
 84          Execution result or error
 85      """
 86      envelope = json.loads(envelope_json)
 87  
 88      # 1. Look up the agent's public key by DID
 89      identity = envelope.get("identity", "")
 90      pub_key_hex = trusted_keys.get(identity)
 91      if pub_key_hex is None:
 92          return {"error": f"Unknown identity: {identity}"}
 93  
 94      # 2. Verify the signature
 95      result = verify_action_envelope(envelope_json, pub_key_hex)
 96      if not result.valid:
 97          return {"error": f"Signature verification failed: {result.error}"}
 98  
 99      # 3. Execute the tool call (application logic)
100      payload = envelope.get("payload", {})
101      tool = payload.get("tool", "unknown")
102      args = payload.get("args", {})
103  
104      print(f"  Verified agent: {identity}")
105      print(f"  Executing tool: {tool}")
106      print(f"  Arguments: {json.dumps(args, indent=2)}")
107  
108      return {"status": "executed", "tool": tool, "result": "success"}
109  
110  
111  # --- Demo ---
112  
113  
114  def main():
115      print("=== Auths AI Agent Signed Tool Call Demo ===\n")
116  
117      # Generate keys for the demo
118      agent_seed, agent_pubkey = generate_test_keypair()
119      agent_did = "did:keri:EAgent123"
120  
121      print(f"Agent DID:        {agent_did}")
122      print(f"Agent public key: {agent_pubkey[:16]}...")
123      print()
124  
125      # Server's trusted key registry
126      trusted_keys = {agent_did: agent_pubkey}
127  
128      # Agent signs a tool call
129      print("1. Agent signs a tool call:")
130      envelope_json = agent_execute_tool(
131          agent_seed_hex=agent_seed,
132          agent_did=agent_did,
133          tool_name="execute_sql",
134          args={"query": "SELECT COUNT(*) FROM users", "database": "analytics"},
135      )
136  
137      envelope = json.loads(envelope_json)
138      print(f"   Type:      {envelope['type']}")
139      print(f"   Identity:  {envelope['identity']}")
140      print(f"   Timestamp: {envelope['timestamp']}")
141      print(f"   Signature: {envelope['signature'][:32]}...")
142      print()
143  
144      # Server verifies and executes
145      print("2. Server verifies and executes:")
146      result = server_verify_and_execute(envelope_json, trusted_keys)
147      print(f"   Result: {result}")
148      print()
149  
150      # Demonstrate rejection of tampered envelope
151      print("3. Tampered envelope is rejected:")
152      tampered = json.loads(envelope_json)
153      tampered["payload"]["args"]["query"] = "DROP TABLE users"
154      tampered_json = json.dumps(tampered)
155  
156      result = server_verify_and_execute(tampered_json, trusted_keys)
157      print(f"   Result: {result}")
158      print()
159  
160      # Demonstrate rejection of unknown identity
161      print("4. Unknown identity is rejected:")
162      unknown = json.loads(envelope_json)
163      unknown["identity"] = "did:keri:EUnknown"
164      unknown_json = json.dumps(unknown)
165  
166      result = server_verify_and_execute(unknown_json, trusted_keys)
167      print(f"   Result: {result}")
168  
169  
170  if __name__ == "__main__":
171      main()