Attestation Signing
HappyView can sign records with an ECDSA (secp256k1) keypair so their origin can be verified later. Lua scripts call atproto.sign() to attach an inline signature to a record and atproto.verify_signature() to check one. HappyView's implementation follows the atproto attestation spec.
How it works
- HappyView loads or generates a secp256k1 keypair on startup
atproto.sign(record)encodes the record to DAG-CBOR, computes its CID, and signs the CID with the private key- The signature is added to the record's
signaturesarray as an inline object atproto.verify_signature(record, sig, repo_did)recomputes the CID and verifies the signature
The repo DID is included in the signed data — a signature for one user's record can't be replayed against another's. Any modification to the record invalidates the signature.
Setup
Attestation signing is enabled by default — HappyView generates a keypair on first startup and persists it to the instance_settings database table. No configuration is required.
To use an explicit key instead, set the ATTESTATION_PRIVATE_KEY environment variable:
| Variable | Required | Default | Description |
|---|---|---|---|
ATTESTATION_PRIVATE_KEY | no | auto-generated | Hex-encoded 32-byte secp256k1 private key |
ATTESTATION_KEY_ID | no | did:web:{host}#attestation | Key identifier included in signatures. Derived from PUBLIC_URL by default |
ATTESTATION_SIG_TYPE | no | app-specific NSID | The $type value used in signature objects |
The key ID defaults to a did:web derived from your PUBLIC_URL. For example, PUBLIC_URL=https://happyview.example.com produces a key ID of did:web:happyview.example.com#attestation.
Priority order
HappyView checks for signing configuration in this order:
- Environment variables — if
ATTESTATION_PRIVATE_KEYis set, it's used - Database — if previously generated keys exist in
instance_settings, they're loaded - Auto-generation — a new key is generated and persisted to the database
If key loading fails for any reason, signing is disabled and atproto.sign / atproto.verify_signature will be nil in Lua scripts.
Using in Lua scripts
Available in queries, procedures, and index hooks via the atproto API.
Signing a record
function handle()
local r = Record(collection, input)
r:save()
local sig = atproto.sign({ text = input.text, createdAt = input.createdAt })
return { uri = r._uri, cid = r._cid, signature = sig }
endThe returned signature object:
{
"$type": "your.app.attestation",
"key": "did:web:happyview.example.com#attestation",
"signature": {
"$bytes": "base64-encoded-signature"
}
}Verifying a signature
function handle()
local record = db.get(params.uri)
if not record then
return { error = "not found" }
end
local sig = record.signatures and record.signatures[1]
if not sig then
return { record = record, verified = false }
end
local valid = atproto.verify_signature(record, sig, record.did)
return { record = record, verified = valid }
endChecking availability
Both functions are nil when no signer is configured:
if atproto.sign then
record.signature = atproto.sign(record)
endSignature format
Signatures are stored as objects in the record's signatures array:
| Field | Type | Description |
|---|---|---|
$type | string | Signature type NSID |
key | string | Key identifier (DID with fragment) |
signature | table | Contains $bytes (base64-encoded) |
Next steps
- atproto API reference —
atproto.signandatproto.verify_signatureparameter docs - Signed Record — save a record with an attestation signature
- Verify Signed Record — fetch a record and verify its signature