Skip to content

Instantly share code, notes, and snippets.

@isaiahbaca
Last active March 4, 2026 18:32
Show Gist options
  • Select an option

  • Save isaiahbaca/673b005aebd659fa617f605348266e7f to your computer and use it in GitHub Desktop.

Select an option

Save isaiahbaca/673b005aebd659fa617f605348266e7f to your computer and use it in GitHub Desktop.

How Trust Packets Work

A Trust Packet is a self-contained, cryptographically signed permission slip. It lets an AI agent prove it has authority to act on someone's behalf — without passwords, API keys, or prior integration.


For Technical Leadership

The Short Version

A Trust Packet is a signed JSON object that answers five questions:

  1. Who authorized this? (the issuer)
  2. Who is acting? (the subject — usually an AI agent)
  3. Who should accept it? (the audience — the merchant or service)
  4. What exactly can they do? (the scope — a narrow, explicit permission)
  5. How do we know it's real? (a cryptographic signature)

Sending Side — The User's Device

The user (or their AI agent) creates a Trust Packet. It's a structured permission slip — it says who's asking, what they want to do, who it's for, and exactly what's allowed. At this point it's just data, no different than any form submission.

Then we sign it. The device takes everything in the packet, runs it through a deterministic process so the data always looks the same regardless of how it traveled, hashes it, and signs that hash with the user's private key. This is the digital equivalent of a notarized signature — it proves the packet came from who it claims, and that nothing was altered.

The signed packet gets sent as plain JSON. Email, API call, QR code — doesn't matter. The transport is irrelevant because the security lives inside the packet, not around it.

Receiving Side — The Merchant or Service

The receiver takes the same data (minus the signature), runs the same deterministic process, and gets the same hash. Then they check: does the signature match this hash, using the sender's public key?

If yes — the packet is authentic. If no — it was forged or tampered with. Reject it.

After that, a few more checks: Is it expired? Is the signer someone we trust? Is the requested action within the stated scope? If anything fails, the whole packet is rejected. No partial trust.

If everything passes, the receiver reads the payload and acts on it.

Why This Matters

There is no pre-registration. No API keys exchanged. No OAuth handshake. No shared database. The sender and receiver don't need any prior relationship.

The receiver just needs one thing: the sender's public key, which is published in DNS the same way email authentication (DKIM) works today.

The packet carries its own proof. That's what makes it portable — it works the same whether it's sent over HTTPS, embedded in a QR code, or passed through three intermediaries. The trust travels with the data.

Example Packet

{
  "header": {
    "tp_version": "1.0",
    "packet_id": "pkt_d9aee3e9da344b108e982d2652e5f633",
    "issued_at": "2026-03-04T12:00:00Z",
    "expires_at": "2026-03-04T12:05:00Z",
    "nonce": "Lk9XQpY7",
    "canonicalization": "unikey-json-v1"
  },
  "claims": {
    "subject": "claude@user.example.com",
    "issuer": "user.example.com",
    "audience": "payments@nike.example.com",
    "scope": ["charge:89.99"]
  },
  "payload": {
    "action": "charge",
    "params": {
      "item": "Nike Air Max",
      "amount": 89.99
    },
    "message": "Purchase Nike Air Max for user."
  },
  "signatures": [
    {
      "algorithm": "ed25519",
      "signer": "user.example.com",
      "key_id": "unikey",
      "signature": "zhlB4tsdRVIV...9gsT8EupBQ=="
    }
  ]
}

Reading this top to bottom:

  • Header — "This is a v1.0 packet, created now, expires in 5 minutes."
  • Claims — "The user authorized their Claude agent to charge $89.99 at Nike."
  • Payload — "Buy Nike Air Max for $89.99."
  • Signatures — "The user's domain signed all of the above with Ed25519."

For Developers

Step-by-Step: Sending a Trust Packet

1. Build the packet — just filling in a form

from unikey_tp import TrustPacket

packet = TrustPacket.build(
    subject="claude@user.example.com",
    issuer="user.example.com",
    audience="payments@nike.example.com",
    scope=["charge:89.99"],
    action="charge",
    params={"item": "Nike Air Max", "amount": 89.99}
)

At this point it's just a dictionary. No security yet. Anyone could have made it.

2. Sign it — this is where the crypto happens

packet.sign(private_key)

What this actually does under the hood:

Take the header + claims + payload
        |
Sort all the keys alphabetically (canonicalize)
        |
Squash it into a single JSON string, no spaces
        |
SHA-256 hash that string --> the Trust Packet Hash (TPH)
        |
Sign the TPH with your private key --> the signature
        |
Attach the signature to the packet

Now the packet is sealed. Change anything and the signature won't match anymore.

3. Send it — just JSON over the wire

import requests

requests.post(
    "https://payments.nike.example.com/charge",
    json=packet.to_dict()
)

It's just a POST with a JSON body. Nothing special about the transport.

Step-by-Step: Receiving and Verifying

4. Receive it — parse the JSON

raw = request.get_json()

5. Verify it — the reverse of signing

result = TrustPacket.verify(raw, public_key)

What this does under the hood:

Take the header + claims + payload (ignore signatures)
        |
Canonicalize it the exact same way
        |
SHA-256 hash it --> should produce the same TPH
        |
Grab the signature from the packet
        |
Use the sender's PUBLIC key to check:
  "does this signature match this hash?"
        |
Yes --> the packet is authentic and untampered
No  --> reject it

Then it also checks:

  • Is it expired? Reject.
  • Is the signer trusted? Reject.
  • Is the scope allowed? Reject.

6. Act on it — read the payload

if result.valid:
    action = raw["payload"]["action"]         # "charge"
    amount = raw["payload"]["params"]["amount"]  # 89.99
    # actually charge the card

Key Concept: Public/Private Keys

The sender and receiver never talk to each other beforehand. There's no login, no session, no shared secret. The receiver just needs the sender's public key (published in DNS, like email does with DKIM).

Sender has:  private key  (secret, never shared — signs things)
Receiver has: public key  (public, published in DNS — verifies things)

The packet carries everything needed to verify itself. That's the whole trick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment