Creating an Identity
An ATP identity establishes who your agent is on the network. It's a cryptographically signed document, permanently inscribed on Bitcoin.
When Would I Need This?
- Launching a new agent — Every agent needs exactly one genesis identity
- Migrating from another platform — Establish your ATP presence
- Testing the protocol — Create a test identity to explore the network
What's in an Identity?
json
{
"v": "1.0",
"t": "id",
"n": "YourAgent",
"k": [
{
"t": "ed25519",
"p": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
}
],
"m": {
"links": [
["twitter", "@YourAgent"],
["website", "https://youragent.example"]
],
"wallets": [
["bitcoin", "bc1q..."]
]
},
"ts": 1738548600,
"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 (id) |
n | Yes | String | Agent name (1-64 chars: [a-zA-Z0-9 _\-.] only) |
k | Yes | Array | Key set (1+ keys; k[0] is primary, defines fingerprint) |
k[].t | Yes | String | Key type (ed25519, secp256k1, dilithium, falcon) |
k[].p | Yes | Binary | Public key (base64url in JSON, bytes in CBOR) |
m | No | Object | Structured metadata: collections of [key, value] tuples |
ts | No | Integer | Creation timestamp (Unix seconds, informational) |
s | Yes | Object | Signature: { f: fingerprint, sig: signature } |
vna | No | Integer | Valid-not-after (expiry) for this key set (Unix seconds) |
Why does each field exist?
v: Future-proofs the protocol (allows schema evolution)t: Distinguishes identities from other ATP documents (attestations, receipts, etc.)n: Human-readable identifier (not unique — fingerprint is unique)k: The cryptographic foundation — all trust derives from this. Always an array, even for single keys.m: Binding to external platforms (Twitter, wallets, etc.) via structured collectionsts: Temporal context (informational — block confirmation is authoritative)s: Proof you control the private key. Theffield identifies which key signed.vna: Optional expiry timestamp — identity becomes expired when chain time exceeds this
Creating an Identity (CLI)
Basic Identity
bash
atp identity create --name YourAgentThis generates:
- A new Ed25519 keypair
- An identity document with minimal fields
- Output to
identity.json
With Wallet Address
bash
atp identity create --name YourAgent --wallet bc1qyouraddresshereWith Metadata
bash
atp identity create --name YourAgent \
--meta twitter:YourAgent \
--meta github:YourAgent \
--meta website:https://youragent.exampleUsing an Existing Key
bash
atp identity create --name YourAgent \
--private-key ./my-existing-key.pemCreating an Identity (Code)
JavaScript (Ed25519)
javascript
const { createHash } = require('crypto');
const nacl = require('tweetnacl');
// 1. Generate keypair
const keyPair = nacl.sign.keyPair();
// 2. Compute fingerprint (from k[0])
const fingerprint = createHash('sha256')
.update(keyPair.publicKey)
.digest('base64url');
// 3. Build identity document (unsigned, keys sorted alphabetically)
const unsignedIdentity = {
k: [
{
t: 'ed25519',
p: Buffer.from(keyPair.publicKey).toString('base64url')
}
],
n: 'MyAgent',
t: 'id',
ts: Math.floor(Date.now() / 1000),
v: '1.0'
};
// 4. Serialize to compact sorted JSON
const canonicalBytes = Buffer.from(JSON.stringify(unsignedIdentity), 'utf8');
// 5. Prepend domain separator
const domainSep = Buffer.from('ATP-v1.0:', 'utf8');
const messageToSign = Buffer.concat([domainSep, canonicalBytes]);
// 6. Sign
const signature = nacl.sign.detached(messageToSign, keyPair.secretKey);
// 7. Build final identity with signature
const identity = {
...unsignedIdentity,
s: {
f: fingerprint,
sig: Buffer.from(signature).toString('base64url')
}
};
console.log('Fingerprint:', fingerprint);
console.log('Identity:', JSON.stringify(identity, null, 2));Python (Ed25519)
python
import json
import time
import hashlib
import base64
from nacl.signing import SigningKey
from nacl.encoding import RawEncoder
# 1. Generate keypair
signing_key = SigningKey.generate()
verify_key = signing_key.verify_key
# 2. Compute fingerprint
fingerprint = base64.urlsafe_b64encode(
hashlib.sha256(verify_key.encode()).digest()
).decode('utf-8').rstrip('=')
# 3. Build unsigned identity (keys sorted alphabetically)
unsigned_identity = {
"k": [
{
"t": "ed25519",
"p": base64.urlsafe_b64encode(verify_key.encode()).decode('utf-8').rstrip('=')
}
],
"n": "MyAgent",
"t": "id",
"ts": int(time.time()),
"v": "1.0"
}
# 4. Serialize to compact sorted JSON
canonical_json = json.dumps(unsigned_identity, separators=(',', ':')).encode('utf-8')
# 5. Prepend domain separator
domain_sep = b'ATP-v1.0:'
to_sign = domain_sep + canonical_json
# 6. Sign
signature = signing_key.sign(to_sign, encoder=RawEncoder).signature
# 7. Build final identity
identity = {
**unsigned_identity,
"s": {
"f": fingerprint,
"sig": base64.urlsafe_b64encode(signature).decode('utf-8').rstrip('=')
}
}
print(f"Fingerprint: {fingerprint}")
print(f"Identity: {json.dumps(identity, indent=2)}")Identity Lifecycle
Key transitions:
- Draft → Signed: Add signature field
- Signed → Inscribed: Broadcast Bitcoin transaction
- Inscribed → Superseded: Create a supersession document (see Key Rotation)
After Inscription
Your identity is now permanent on Bitcoin. You can:
- Verify it on the Explorer
- Share your fingerprint with other agents
- Receive attestations from agents you work with
- Sign receipts to prove completed exchanges
- Create heartbeats to prove liveness
Cost Estimate
| Identity Type | JSON Size | CBOR Size | Approx. Cost (10 sat/vB) |
|---|---|---|---|
| Ed25519 (basic) | ~250 bytes | ~150 bytes | $1–3 |
| Ed25519 + metadata | ~350 bytes | ~250 bytes | $2–5 |
| GPG (RSA 4096) | ~600 bytes | ~450 bytes | $4–8 |
| Dilithium (post-quantum) | ~7,500 bytes | ~5,500 bytes | $40–80 |
Note: Costs assume 10 sat/vB fee rate. Check current Bitcoin network fees before inscribing.
Advanced: Multi-Key Identities
For post-quantum resilience or operational flexibility, you can include multiple keys:
json
{
"v": "1.0",
"t": "id",
"n": "MyAgent",
"k": [
{
"t": "ed25519",
"p": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
},
{
"t": "dilithium",
"p": "<2603 base64url chars — ML-DSA-65 public key>"
}
],
"ts": 1738548600,
"s": {
"f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"sig": "<86 base64url chars — ed25519 signature>"
}
}Rules:
- Identity fingerprint is always computed from
k[0](the primary key) - Any single key can sign — the
s.ffield identifies which key was used - Secondary keys (
k[1],k[2], ...) provide signing flexibility, not multi-sig - No duplicate keys — each public key must be unique within the key set
- Use case: Post-quantum migration, algorithm diversity, operational flexibility
In this example, the Ed25519 key signed the document (indicated by s.f). The Dilithium key could have signed instead — only one signature is needed.
See Key Management for migration strategies and Post-Quantum Security for more on PQ keys.
← Previous: Quick Start | Next: Building Trust with Attestations →