- Guides
- Data
Attestation signing
HappyView can sign records with an ECDSA (secp256k1) keypair so their origin can be verified later. Scripts call sign on happyview.atproto to attach an inline signature to a record and verify_signature to check one. HappyView’s implementation follows the atproto attestation spec.
Signing and verification
Section titled “Signing and verification”- 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 keysignreturns the inline signature object, for the script to add to the record’ssignaturesarrayatproto.verify_signature(record, sig, repo_did)recomputes the CID and verifies the signature
The repo DID is included in the signed data, so a signature for one user’s record can’t be replayed against another’s. Any modification to the record invalidates the signature.
Attestation signing is enabled by default. HappyView generates a keypair on first startup and persists it to the happyview_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_ | no | auto-generated | Hex-encoded 32-byte secp256k1 private key |
ATTESTATION_ | no | did:web:localhost#attestation | Key identifier included in signatures. Read only when ATTESTATION_PRIVATE_KEY is set |
ATTESTATION_ | no | games.gamesgamesgamesgames.attestation | The $type value used in signature objects. Read only when ATTESTATION_PRIVATE_KEY is set |
A generated key takes the default $type, and a key ID derived from your PUBLIC_URL. For example, PUBLIC_URL=https://happyview.example.com produces a key ID of did:web:happyview.example.com#attestation. Set ATTESTATION_KEY_ID whenever you set ATTESTATION_PRIVATE_KEY, or verifiers get a localhost key ID.
Priority order
Section titled “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
happyview_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 both sign and verify_signature raise NO_SIGNER.
Using in scripts
Section titled “Using in scripts”Both functions live on the happyview.atproto library, available in every script kind.
Signing a record
Section titled “Signing a record”local time = require("internal.time")local record = require("happyview.record")local atproto = require("happyview.atproto")
function handle(input, ctx) local body = { text = input.text, createdAt = time.to_iso8601(time.now()) }
local sig = atproto.sign(body) body.signatures = { sig }
local ref = record.create(ctx.collection, body) return { uri = ref.uri, cid = ref.cid, signature = sig }endThe returned signature object:
{ "$type": "games.gamesgamesgamesgames.attestation", "key": "did:web:happyview.example.com#attestation", "signature": { "$bytes": "base64-encoded-signature" }}sign signs as ctx.caller_did, since the DID is part of the signed content; it raises BAD_INPUT from a script with no caller.
Verifying a signature
Section titled “Verifying a signature”local db = require("happyview.db")local atproto = require("happyview.atproto")
function handle(input, ctx) local row = db.get(input.uri) if not row then return { error = "not found" } end local record = row.record
local sig = record.signatures and record.signatures[1] if not sig then return { record = record, verified = false } end
local ok, valid = pcall(atproto.verify_signature, record, sig, row.did) if not ok then -- Couldn't check, which is not the same as "the signature is bad". return { record = record, verified = nil, error = tostring(valid) } end return { record = record, verified = valid }endverify_signature returns false only when it checked the signature and it didn’t match. It raises UNVERIFIABLE when it couldn’t check: signature bytes that aren’t valid base64, a missing field, or a record that won’t encode. Keep those apart. A script that treats “couldn’t check” as false reports a fault in its own verification path as a forged record, which is a serious thing to tell a user about their own data.
Checking availability
Section titled “Checking availability”Both functions raise NO_SIGNER when no signer is configured. A script that should work either way wraps the call:
local ok, sig = pcall(atproto.sign, body)if ok then body.signatures = { sig }endSignature format
Section titled “Signature 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) |
Security considerations
Section titled “Security considerations”sign exposes the instance’s signing key to scripts. Treat it as a
privileged capability:
- It signs exactly what you give it. A signature only proves “this HappyView instance signed this content”. It does not prove the content is authentic, is present in anyone’s repo, or was authored by any particular DID. Only sign content you have already verified.
- It’s available to any script with a caller, including a record script triggered by an arbitrary network record, where the caller is the record’s repo DID. Don’t sign untrusted input (a network record, a request parameter) unless you intend the instance to vouch for it.
- Installing
happyview.atprotograntsattest:sign. Creating scripts requires thescripts:managepermission, and deploying a project requiresproject:deploy. The signing key is therefore only reachable by operators you have trusted with one of those permissions, so grant them accordingly, and review scripts that callsign.
If you need signatures scoped to a specific verified subject, have the script
verify the subject itself (e.g. confirm the record’s did and content against the
source) before calling sign.
Next steps
Section titled “Next steps”- Libraries:
happyview.atprotoand where its full surface is documented - Signed record: save a record with an attestation signature
- Verify a signed record: fetch a record and verify its signature
© HappyProto

