Skip to content
Lyric Scoring Standard
Reproducibility

Verify a SongForgeAI seal in 60 seconds.

Every /api/v1/score response carries an ed25519 signature over the rubric version + model + temperature + build SHA. Anyone with the public key (served at /.well-known/songforgeai-pubkey.json) can verify a score independently. No SongForgeAI infrastructure required; no trust assumed. Math, not promises.

1. Try it now (browser)

Paste a seal + signature + public key from any score response, or click Use the bundled example to load a known-good fixture. The verification runs entirely in your browser — we never see your seal.

2. Verify from a shell

One-liner: fetch a score, fetch the public key, run ed25519 verification with @noble/ed25519.

# 1. Score some lyrics + capture the seal block
curl -s -X POST https://songforgeai.com/api/v1/score \
  -H "Authorization: Bearer $SFAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lyrics": "..."}' \
  | tee score.json

# 2. Pull the active public key
curl -s https://songforgeai.com/.well-known/songforgeai-pubkey.json > pubkey.json

# 3. Verify (pure node, no extra packages)
node -e "
  const { verify } = require('@noble/ed25519');
  const { sha512 } = require('@noble/hashes/sha2.js');
  require('@noble/ed25519').hashes.sha512 = (m) => sha512(m);

  const score = require('./score.json');
  const pubkey = require('./pubkey.json');

  const seal = { ...score.seal };
  const sig = seal.signature;
  delete seal.signature;

  const canonical = (o) => o === null || typeof o !== 'object'
    ? JSON.stringify(o)
    : Array.isArray(o)
      ? '[' + o.map(canonical).join(',') + ']'
      : '{' + Object.keys(o).sort().map(k =>
          JSON.stringify(k) + ':' + canonical(o[k])
        ).join(',') + '}';

  const message = new TextEncoder().encode(canonical(seal));
  const sigBytes = Uint8Array.from(atob(sig.signature), c => c.charCodeAt(0));
  const pkBytes = Buffer.from(pubkey.current.keyHex, 'hex');

  verify(sigBytes, message, pkBytes).then(ok =>
    console.log(ok ? '✓ valid' : '✗ INVALID')
  );
"

3. Verify from TypeScript / JavaScript

Use @songforgeai/client to fetch the score, then verify with @noble/ed25519 (a tiny zero-dep crypto library). The SDK stays dependency-free; you opt in to crypto only when you want it.

import { SongForgeAI } from '@songforgeai/client';
import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha2';
ed.hashes.sha512 = (m) => sha512(m);

const sf = new SongForgeAI({ apiKey: process.env.SFAI_API_KEY! });
const score = await sf.score({ lyrics: '...', genre: 'country' });

const pubkey = await fetch(
  'https://songforgeai.com/.well-known/songforgeai-pubkey.json'
).then((r) => r.json());

const canonical = (o) => o === null || typeof o !== 'object'
  ? JSON.stringify(o)
  : Array.isArray(o)
    ? '[' + o.map(canonical).join(',') + ']'
    : '{' + Object.keys(o).sort().map((k) =>
        JSON.stringify(k) + ':' + canonical(o[k])
      ).join(',') + '}';

const sealCopy = { ...score.seal };
const sig = sealCopy.signature;
delete sealCopy.signature;

const message = new TextEncoder().encode(canonical(sealCopy));
const sigBytes = Uint8Array.from(atob(sig.signature), (c) => c.charCodeAt(0));
const pkBytes = Buffer.from(pubkey.current.keyHex, 'hex');

const ok = await ed.verify(sigBytes, message, pkBytes);
console.log(ok ? 'verified' : 'rejected');

SDK on npm · Full API reference

What gets signed (and what doesn't)

The signature covers the seal block only: rubric version, model, temperature, build SHA, build number. NOT the individual metric values, NOT the lyrics, NOT the wounds.

Why: the seal is the recipe. If two third parties both sign the same recipe and run it, they should get the same numbers (or near-same — temperature adds bounded variance). Verifying the seal = verifying that this score came out of this recipe. Verifying the metric numbers themselves is a separate problem (re-run the model, compare outputs).

The full pipeline contract is documented at /scoring/standard/whitepaper (CC BY 4.0). The model card lives at /scoring/standard/model-card.