Trillboards
Back to Developers
Developer Docs

Proof-of-Play.

Cryptographic ad verification with Ed25519 signed proofs

How It Works

Every ad impression on Trillboards is cryptographically signed. This creates an immutable, independently verifiable proof that an ad actually played on a specific screen.

Step 1

Ad Plays on Screen

When an ad plays, the SDK captures impression details β€” ad ID, screen ID, device ID, and a precise timestamp.

Step 2

Cryptographic Signature

The impression payload is signed with Ed25519 using Trillboards' private key, creating a tamper-proof proof.

Step 3

Open Verification

Anyone can verify the proof using the public key β€” no API access needed. The signature proves the ad actually played.

Open Verification, Closed Discovery

Trillboards uses an "Open Verification, Closed Discovery" model:

βœ“

Open Verification

Anyone with a proof and the public key can verify it. No authentication needed. No API key required.

Closed Discovery

Listing proofs requires authentication. Only campaign owners and authorized partners can browse proof history.

Signature Format

All proofs use Ed25519 (version 2). The signed payload is constructed by concatenating fields with dots:

Signature payload format
v2.<timestamp>.<adId>.<impressionId>.<screenId>.<deviceId>
FieldTypeDescription
versionstring"v2" β€” always the literal string
timestampnumberUnix epoch milliseconds when the ad played
adIdstringCampaign or ad creative identifier
impressionIdstringUnique impression ID
screenIdstringScreen/location identifier
deviceIdstringPhysical device identifier

The signature is prefixed with <code>ed25519=</code> followed by the hex-encoded signature bytes.

Verification Examples

Node.js

verify-proof.js
const crypto = require('crypto');

// Fetch from /v1/advertiser/proof/.well-known/public-key
const PUBLIC_KEY_HEX = 'a5a9c43785680c62ebcd01ec49a0d6055fd140023df0f142c3e37c6abafce34f';

function verifyProof(proof) {
  const payload = `v2.${proof.timestamp}.${proof.adId}.${proof.impressionId}.${proof.screenId}.${proof.deviceId}`;

  const signatureHex = proof.signature.replace('ed25519=', '');
  const signatureBytes = Buffer.from(signatureHex, 'hex');

  const publicKey = crypto.createPublicKey({
    key: Buffer.concat([
      Buffer.from('302a300506032b6570032100', 'hex'),
      Buffer.from(PUBLIC_KEY_HEX, 'hex')
    ]),
    format: 'der',
    type: 'spki'
  });

  return crypto.verify(null, Buffer.from(payload), publicKey, signatureBytes);
}

const proof = {
  impressionId: '507f1f77bcf86cd799439011',
  adId: 'campaign-abc123',
  screenId: 'screen-xyz789',
  deviceId: 'device-456',
  timestamp: 1705401600000,
  signature: 'ed25519=a1b2c3d4e5f6...'
};

console.log('Valid:', verifyProof(proof));

Python

verify_proof.py
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignature

PUBLIC_KEY_HEX = 'a5a9c43785680c62ebcd01ec49a0d6055fd140023df0f142c3e37c6abafce34f'

def verify_proof(proof):
    verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY_HEX))

    payload = f"v2.{proof['timestamp']}.{proof['adId']}.{proof['impressionId']}.{proof['screenId']}.{proof['deviceId']}"

    signature_hex = proof['signature'].replace('ed25519=', '')
    signature_bytes = bytes.fromhex(signature_hex)

    try:
        verify_key.verify(payload.encode(), signature_bytes)
        return True
    except BadSignature:
        return False

cURL

terminal
# Verify a proof using the API (no auth required)
curl -X POST https://api.trillboards.com/v1/advertiser/proof/verify-proof \
  -H "Content-Type: application/json" \
  -d '{
    "signature": "ed25519=a1b2c3d4e5f6...",
    "timestamp": 1705401600000,
    "adId": "campaign-abc123",
    "impressionId": "507f1f77bcf86cd799439011",
    "screenId": "screen-xyz789",
    "deviceId": "device-456"
  }'

# Response:
# {
#   "success": true,
#   "verification": {
#     "valid": true,
#     "version": "v2"
#   }
# }

API Endpoints

MethodPathAuthDescription
GET/v1/advertiser/proof/.well-known/public-keyNoneReturns the Ed25519 public key used to sign all proofs.
POST/v1/advertiser/proof/verify-proofNoneSubmit a proof for server-side verification. Returns validity and version.
GET/v1/advertiser/proof/proofsBearer tokenList proofs for your campaigns with pagination. Query params: campaign_id, start_date, end_date, limit, offset.
GET/v1/advertiser/proof/proofs/:idBearer tokenGet a single proof by ID with full details.

Why It Matters

Tamper-Proof

Ed25519 signatures are impossible to forge. Every proof is mathematically verifiable.

Open Verification

No API key needed to verify. Anyone can confirm an ad played using the public key.

Closed Discovery

Only authenticated partners can list their proofs. You control who sees your impression data.

Unique Per-Impression

Every single impression gets its own unique signature. No batching, no aggregation, no approximation.