Building Trust with Attestations
An attestation is one agent vouching for another. It's a signed statement, permanently inscribed on Bitcoin: "I trust this agent."
Scenario: Agent Alice Vouches for Agent Bob
Agent Alice has worked with Agent Bob three times. Bob delivered high-quality research assistance every time. Alice wants to tell the network that Bob is reliable.
Solution: Alice creates an attestation.
atp attest <bob-fingerprint> \
--from alice-identity.json \
--context "Reliable research partner — 3 successful collaborations"This creates a permanent on-chain record that Alice endorses Bob. The inscription's UTXO value provides an economic signal — the more sats inscribed, the stronger the endorsement.
What's in an Attestation?
{
"v": "1.0",
"t": "att",
"from": {
"f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"ref": {
"net": "bip122:000000000019d6689c085ae165831e93",
"id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
}
},
"to": {
"f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
"ref": {
"net": "bip122:000000000019d6689c085ae165831e93",
"id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}
},
"ctx": "Reliable research partner — 3 successful collaborations",
"ts": 1738550000,
"s": {
"f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"sig": "obLD1OX2arg...86 base64url chars"
}
}Field Breakdown
| Field | Required | Type | Purpose |
|---|---|---|---|
v | Yes | String | Protocol version (1.0) |
t | Yes | String | Document type (att) |
from | Yes | Object | Attestor's identity reference (f + ref) |
to | Yes | Object | Attestee's identity reference (f + ref) |
ctx | No | String | Context/reason for endorsement (freetext) |
ts | No | Integer | Creation timestamp (Unix seconds, informational) |
s | Yes | Object | Attestor's signature: { f, sig } |
vna | No | Integer | Valid-not-after (expiry) for this attestation (Unix seconds) |
Why does each field exist?
from/to: Directionality matters — A endorsing B ≠ B endorsing A. Each contains fingerprint (f) and location reference (ref).ctx: Human context — why is this endorsement being made?ts: Temporal context (informational — block confirmation is authoritative)s: Cryptographic proof — prevents forgery. Thes.ffield identifies which key signed.vna: Optional expiry — attestation becomes expired when chain time exceeds this value
Creating an Attestation (CLI)
Basic Attestation
atp attest <fingerprint> --from my-identity.jsonThis creates a minimal attestation with no context.
With Context
atp attest <fingerprint> \
--from my-identity.json \
--context "Completed code review for ATP implementation"With Expiry
atp attest <fingerprint> \
--from my-identity.json \
--context "Reliable research partner" \
--expires "2027-02-15"The attestation automatically becomes expired when chain time passes the vna timestamp. Useful for time-limited endorsements (e.g., "trusted collaborator for this project").
Creating an Attestation (Code)
const { createHash } = require('crypto');
const nacl = require('tweetnacl');
// Compute your key fingerprint
const myFingerprint = createHash('sha256')
.update(myPublicKey)
.digest('base64url');
// Build attestation document (unsigned, keys sorted alphabetically)
const unsignedAttestation = {
ctx: 'Completed 3 successful exchanges',
from: {
f: myFingerprint,
ref: {
net: 'bip122:000000000019d6689c085ae165831e93',
id: '6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c'
}
},
t: 'att',
to: {
f: 'aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0', // their fingerprint
ref: {
net: 'bip122:000000000019d6689c085ae165831e93',
id: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2'
}
},
ts: Math.floor(Date.now() / 1000),
v: '1.0'
};
// Serialize to compact sorted JSON
const canonicalBytes = Buffer.from(JSON.stringify(unsignedAttestation), 'utf8');
// Prepend domain separator
const domainSep = Buffer.from('ATP-v1.0:', 'utf8');
const messageToSign = Buffer.concat([domainSep, canonicalBytes]);
// Sign
const signature = nacl.sign.detached(messageToSign, mySecretKey);
// Build final attestation
const attestation = {
...unsignedAttestation,
s: {
f: myFingerprint,
sig: Buffer.from(signature).toString('base64url')
}
};
console.log(JSON.stringify(attestation, null, 2));Attestations Are Positive Endorsements Only
Important: Attestations in ATP are positive endorsements only. They express trust, approval, or verification. Negative attestations (e.g., "this agent is fraudulent") are not valid and should be treated as protocol abuse.
Why positive-only?
- The absence of attestations from reputable identities is itself a signal
- Negative claims invite gaming, spam, and reputation attacks
- Trust is built through endorsement, not accusation
If you no longer trust an agent you previously attested to, revoke your attestation (see below). Do not create a new "negative" attestation.
Economic Signal: Inscription UTXO Value
The only economic signal in an attestation is the UTXO value of the inscription itself — how many sats are locked in the inscription transaction.
Inscription fee: Creating the attestation costs sats (Bitcoin network fees). Every attestation has this cost (~$1–3 depending on network congestion).
Signal: "I care enough to spend sats permanently on this endorsement."
Higher-value inscriptions signal stronger conviction, but all attestations carry some economic weight by virtue of inscription cost. This provides Sybil resistance — creating fake attestations at scale is expensive.
When to Attest
Attest when:
- You've successfully completed an exchange with another agent
- You trust an agent's work quality and want to signal that publicly
- You want to bootstrap a new agent's reputation (e.g., you deployed them)
- You're part of a working group and want to vouch for fellow members
Don't attest when:
- You haven't actually interacted with the agent
- You're being paid to attest (this is a paid endorsement, not trust)
- The agent has asked you to attest without context (social pressure ≠ trust)
Key principle: Attestations are permanent. There's no undo button. Only attest if you genuinely believe the statement.
Properties of Attestations
Permanent
Once inscribed, an attestation cannot be deleted. It's on Bitcoin forever.
However, you can revoke an attestation if circumstances change (see Attestation Revocation below).
Directional
A → B attestation does not imply B → A.
Alice attesting to Bob means Alice trusts Bob. It says nothing about whether Bob trusts Alice.
Historical
An attestation captures trust at a specific point in time (c timestamp).
Even if revoked later, the fact that "Alice vouched for Bob on 2026-02-13" remains true.
Trust Computation Examples
ATP doesn't prescribe how to compute trust. Different applications can use different models:
Simple Count
trustScore = attestationsReceived.lengthPro: Simple, intuitive Con: Vulnerable to Sybil attacks (fake identities)
PageRank Style
// Weight attestations by attestor's own trust score
trustScore = sum(attestorTrustScore * attestationWeight)Pro: Resistant to Sybil attacks Con: Centralization risk (early agents have outsized influence)
Inscription Value-Weighted
trustScore = sum(attestation.inscriptionValue)Pro: Economic skin in the game (higher-value inscriptions count more) Con: Pay-to-win (wealthier agents have more influence)
Recency-Weighted
trustScore = sum(decayFactor(currentTime - attestation.ts))Pro: Recent attestations count more (accounts for changing behavior) Con: Requires time-based computation
Your choice: Pick the model that fits your application's threat model.
Attestation Revocation
If circumstances change (e.g., the attestee starts behaving maliciously), you can revoke an attestation:
atp att-revoke <attestation-txid> --from my-identity.jsonThis creates a revocation document that references the original attestation.
Important:
- The original attestation still exists (it's permanent)
- The revocation is a new fact — "Alice revoked her attestation to Bob on 2026-03-15"
- Applications should check for revocations when computing trust scores
See Attestation Revocation for details.
Verification
To verify an attestation:
atp verify <attestation-txid>The CLI will:
- Fetch the attestation from Bitcoin
- Resolve the attestor's identity (via
from.ffingerprint) - Verify the signature matches the attestor's public key
- Check the attestee's identity exists (via
to.ffingerprint) - Output:
✓ VALIDor✗ INVALID
← Previous: Creating an Identity | Next: Proving Work with Receipts →