<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: willamhou</title>
    <description>The latest articles on DEV Community by willamhou (@willamhou).</description>
    <link>https://dev.to/willamhou</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3856634%2F4545fd93-d2d7-46b4-a778-faf990fee34f.jpg</url>
      <title>DEV Community: willamhou</title>
      <link>https://dev.to/willamhou</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/willamhou"/>
    <language>en</language>
    <item>
      <title>How I Built Cryptographic Signing for Every AI Agent Tool Call</title>
      <dc:creator>willamhou</dc:creator>
      <pubDate>Thu, 02 Apr 2026 02:45:56 +0000</pubDate>
      <link>https://dev.to/willamhou/how-i-built-cryptographic-signing-for-every-ai-agent-tool-call-1f6a</link>
      <guid>https://dev.to/willamhou/how-i-built-cryptographic-signing-for-every-ai-agent-tool-call-1f6a</guid>
      <description>&lt;h1&gt;
  
  
  How I Built Cryptographic Signing for Every AI Agent Tool Call
&lt;/h1&gt;

&lt;p&gt;Your AI agent just mass-deleted a production database. Can you prove exactly what it did? When? Who authorized it?&lt;/p&gt;

&lt;p&gt;You can't. None of the major agent frameworks produce cryptographic evidence of what happened.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/Prismer-AI/signet" rel="noopener noreferrer"&gt;Signet&lt;/a&gt; to fix this. It gives every AI agent an Ed25519 identity and signs every tool call with a tamper-evident receipt. Open source, 3 lines of code to integrate.&lt;/p&gt;

&lt;p&gt;This post covers the design decisions, the crypto choices, and why I built an SDK instead of a proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) is becoming the standard for agent-tool communication. Claude, Cursor, Windsurf, and dozens of other tools use it. But MCP has no signing, no audit log, and no way to prove which agent did what.&lt;/p&gt;

&lt;p&gt;Real incidents that motivated this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Kiro deleted a production environment&lt;/li&gt;
&lt;li&gt;Replit agent dropped a live database
&lt;/li&gt;
&lt;li&gt;Supabase MCP leaked tokens via prompt injection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every case: zero audit trail. The agent did something, nobody could prove exactly what.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SDK, not proxy
&lt;/h3&gt;

&lt;p&gt;The existing tools in this space (Aegis, estoppl) use a proxy/gateway model. You deploy a separate process that sits between your agent and the MCP server, intercepting all traffic.&lt;/p&gt;

&lt;p&gt;I chose the opposite: a client-side SDK. Three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero infrastructure.&lt;/strong&gt; &lt;code&gt;npm install&lt;/code&gt; or &lt;code&gt;pip install&lt;/code&gt;, not Docker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stdio-native.&lt;/strong&gt; Most MCP connections use stdio pipes. Proxying stdio requires process orchestration that adds failure modes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework-agnostic.&lt;/strong&gt; Works with any MCP client, any transport, any language.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoff: a proxy can enforce policies (block unauthorized calls). Signet can't — it's a camera, not a bouncer. Attestation, not prevention. I think both are needed, and they're complementary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ed25519, not ECDSA or RSA
&lt;/h3&gt;

&lt;p&gt;Ed25519 was the obvious choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast.&lt;/strong&gt; ~70,000 signatures/sec on a laptop. Agent tool calls are maybe 10/sec. No bottleneck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small.&lt;/strong&gt; 64-byte signatures, 32-byte keys. Fits in JSON metadata without bloat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic.&lt;/strong&gt; Same key + same message = same signature. No nonce generation needed at signing time (the nonce is in the key schedule).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battle-tested.&lt;/strong&gt; SSH, Signal, age, WireGuard all use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use &lt;code&gt;ed25519-dalek&lt;/code&gt; in Rust. The same core compiles to WASM for Node.js and to native for Python via PyO3. One implementation, three languages, zero divergence risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  RFC 8785 (JCS) for canonical JSON
&lt;/h3&gt;

&lt;p&gt;The signature covers the entire receipt body: action, signer, timestamp, nonce. But JSON serialization is non-deterministic — &lt;code&gt;{"a":1,"b":2}&lt;/code&gt; and &lt;code&gt;{"b":2,"a":1}&lt;/code&gt; are semantically identical but produce different bytes.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://datatracker.ietf.org/doc/html/rfc8785" rel="noopener noreferrer"&gt;RFC 8785 (JSON Canonicalization Scheme)&lt;/a&gt; to solve this. JCS defines a deterministic JSON serialization: sorted keys, no whitespace, specific number formatting. Sign the JCS output, verify against the JCS output. Deterministic.&lt;/p&gt;

&lt;p&gt;Why JCS over alternatives like bencode or CBOR? Because the receipts are JSON, the MCP protocol is JSON, and I didn't want to introduce a second serialization format just for signing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hash-chained audit log
&lt;/h3&gt;

&lt;p&gt;Every receipt is appended to a local JSONL audit log. Each entry includes the SHA-256 hash of the previous entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;record_&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;receipt:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prev_hash:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:0000..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;record_hash:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:abc1..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;record_&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;receipt:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prev_hash:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:abc1..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;record_hash:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:def2..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;record_&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;receipt:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prev_hash:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:def2..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;record_hash:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:ghi3..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete or modify any record and the chain breaks. &lt;code&gt;signet verify --chain&lt;/code&gt; detects it.&lt;/p&gt;

&lt;p&gt;This is tamper-&lt;em&gt;evident&lt;/em&gt;, not tamper-&lt;em&gt;proof&lt;/em&gt;. Someone with disk access can delete the entire log. The hash chain catches selective editing (changing one record while keeping others). Off-host anchoring (anchoring chain hashes to an external service) is on the roadmap for true tamper-proof guarantees.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encrypted key storage
&lt;/h3&gt;

&lt;p&gt;Agent keys are encrypted at rest with Argon2id + XChaCha20-Poly1305:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Argon2id&lt;/strong&gt; for key derivation (OWASP recommended, memory-hard, resists GPU attacks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XChaCha20-Poly1305&lt;/strong&gt; for encryption (24-byte nonce = safe random nonce generation, AEAD for authenticated encryption)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AAD&lt;/strong&gt; (Additional Authenticated Data) binds the ciphertext to the key's metadata, preventing key-file swaps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unencrypted keys are supported for CI/automation (&lt;code&gt;--unencrypted&lt;/code&gt; flag). Keys stored at &lt;code&gt;~/.signet/keys/&lt;/code&gt; with &lt;code&gt;0600&lt;/code&gt; permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Receipt Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rec_e7039e7e7714e84f..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github_create_issue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fix bug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:b878192252cb..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp://github.local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"transport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pubkey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ed25519:0CRkURt/tc6r..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo-bot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"willamhou"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-29T23:24:03.309Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rnd_dcd4e135799393..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ed25519:6KUohbnSmehP..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signature covers &lt;code&gt;v + action + signer + ts + nonce&lt;/code&gt; via JCS. Tamper with any field and verification fails.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;params_hash&lt;/code&gt; is always present. By default, raw params are also stored. For sensitive data, &lt;code&gt;--hash-only&lt;/code&gt; mode stores only the hash — you can prove &lt;em&gt;what shape&lt;/em&gt; the params had without revealing their content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration: 3 Lines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TypeScript (MCP)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SigningTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@signet-auth/mcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StdioClientTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-mcp-server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SigningTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;tools/call&lt;/code&gt; request gets a signed receipt injected into &lt;code&gt;params._meta._signet&lt;/code&gt;. MCP servers don't need to change — they ignore unknown fields per the spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python — LangChain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;signet_auth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SigningAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;signet_auth.langchain&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SignetCallbackHandler&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SigningAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;willamhou&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SignetCallbackHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Pass handler to your LangChain agent — every tool call is now signed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python — CrewAI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;signet_auth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SigningAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;signet_auth.crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;install_hooks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uninstall_hooks&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SigningAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;willamhou&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;install_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# every tool call is now signed
&lt;/span&gt;&lt;span class="nf"&gt;uninstall_hooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python — Low-level API (any framework)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;signet_auth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SigningAgent&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SigningAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;willamhou&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github_create_issue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix bug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All Python bindings are Rust compiled via PyO3 — same crypto, same behavior, native speed. &lt;code&gt;pip install signet-auth&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;signet identity generate &lt;span class="nt"&gt;--name&lt;/span&gt; my-agent
signet sign &lt;span class="nt"&gt;--key&lt;/span&gt; my-agent &lt;span class="nt"&gt;--tool&lt;/span&gt; &lt;span class="s2"&gt;"github_create_issue"&lt;/span&gt; &lt;span class="nt"&gt;--params&lt;/span&gt; &lt;span class="s1"&gt;'{"title":"fix bug"}'&lt;/span&gt;
signet audit &lt;span class="nt"&gt;--since&lt;/span&gt; 24h
signet verify &lt;span class="nt"&gt;--chain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signet/
├── crates/signet-core/       Rust core (one implementation)
├── bindings/
│   ├── signet-ts/            → WASM for Node.js
│   └── signet-py/            → PyO3 for Python
├── packages/
│   ├── @signet-auth/core     TypeScript wrapper
│   └── @signet-auth/mcp      MCP middleware
└── signet-cli/               CLI binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key architectural decision: &lt;strong&gt;one Rust implementation, compiled to multiple targets.&lt;/strong&gt; The WASM binding and Python binding call the same &lt;code&gt;signet-core&lt;/code&gt; code. There is no separate TypeScript or Python reimplementation of the signing logic. This eliminates the "two implementations diverge" class of bugs entirely.&lt;/p&gt;

&lt;p&gt;172 tests across Rust (68), Python (85), TypeScript (11), and WASM (8).&lt;/p&gt;

&lt;h2&gt;
  
  
  What Signet Does NOT Do (Yet)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It proves the agent requested an action, not that the server executed it.&lt;/strong&gt; The receipt says "agent X signed intent to call tool Y with params Z at time T." Whether the server actually did it is a separate question. Server-side verification middleware is on the roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't prevent bad actions.&lt;/strong&gt; Signet is a camera, not a bouncer. It complements prevention tools like policy engines and firewalls. You want both: stop the bad thing AND have evidence of what happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signer identity is self-asserted.&lt;/strong&gt; The &lt;code&gt;signer.name&lt;/code&gt; and &lt;code&gt;signer.owner&lt;/code&gt; fields are set by the agent. There's no external identity registry (yet) to verify that "demo-bot" actually belongs to "willamhou."&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;signet-auth
&lt;span class="c"&gt;# or&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @signet-auth/core @signet-auth/mcp
&lt;span class="c"&gt;# or&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; signet-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/Prismer-AI/signet" rel="noopener noreferrer"&gt;github.com/Prismer-AI/signet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apache-2.0 + MIT dual license.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about the design decisions, crypto choices, or roadmap? I'm &lt;a href="https://x.com/WillamUpUp" rel="noopener noreferrer"&gt;@willamhou&lt;/a&gt; on Twitter/X.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
