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.
A Trust Packet is a signed JSON object that answers five questions:
- Who authorized this? (the issuer)
- Who is acting? (the subject — usually an AI agent)
- Who should accept it? (the audience — the merchant or service)
- What exactly can they do? (the scope — a narrow, explicit permission)
- How do we know it's real? (a cryptographic signature)
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.
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.
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.
{
"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."
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.
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 cardThe 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.