Version 1.0DraftLast updated: February 2025

The RefPassport Standard

Open protocol for DNS-anchored cryptographic employment reference verification. Issue once, verify anywhere. No central authority required.

Abstract: RefPassport defines a protocol for issuing, delivering, and verifying employment references using Ed25519 digital signatures anchored to DNS TXT records. References are portable, tamper-evident, and independently verifiable by any third party without contacting the original issuer.

This is a draft specification. RefPassport v1.0 describes the protocol as currently implemented. We welcome feedback and contributions as it evolves toward a formal open standard.

1. Protocol Overview

RefPassport enables employers to issue cryptographically signed employment references that candidates can carry with them throughout their career. Any third party (a recruiter, HR team, or hiring manager) can verify the authenticity of a reference instantly, without contacting the issuing company or creating an account.

Actors

Issuer

The employer who creates and signs a reference. Publishes their public key in DNS and signs references with their private key.

Candidate

The employee who receives the signed reference. Carries it as a PDF or shareable link and presents it to future employers.

Verifier

Any third party (recruiter, HR) who verifies a reference. Checks the signature against the issuer’s public key via DNS.

Protocol Flow

Issuer generates Ed25519 key pair
Public key published in DNS TXT record
{SELECTOR}._workreferences.{DOMAIN}
Issuer signs reference with private key
Ed25519 detached signature
Candidate receives signed reference
PDF + verification link
Verifier checks signature + DNS

Design Goals

  • Portability: references travel with the candidate, not locked to any platform.
  • Tamper-evidence: any modification to the reference content invalidates the signature.
  • Decentralised trust: verification relies on DNS, not a central certificate authority.
  • Zero-knowledge: the server never has access to plaintext signing keys.
  • Simplicity: inspired by DKIM, the protocol uses well-understood standards (Ed25519, DNS TXT, JSON).

2. DNS Record Specification

The issuer’s Ed25519 public key is published as a DNS TXT record under a well-known subdomain. This is the anchor that allows anyone to independently verify a reference without trusting RefPassport itself.

Record Format

DNS TXT Record
Name:  {SELECTOR}._workreferences.{DOMAIN}
Type:  TXT
Value: v=workreferences1; k=ed25519; p={BASE64_PUBLIC_KEY}

Field Definitions

FieldTypeDescription
SELECTORStringAlphanumeric key selector (e.g. REF1, 2025Q1). Max 63 characters. Case-insensitive. Allows key rotation.
_workreferencesFixedProtocol-reserved subdomain prefix. Must appear exactly as shown.
DOMAINStringThe issuer's domain (e.g. example.com). Must be a domain they control.
vStringProtocol version. Must be "refpassport1" for this version of the standard.
kStringKey algorithm. Must be "ed25519" in v1. Future versions may support additional algorithms.
pBase64The Ed25519 public key, base64-encoded. 32 bytes (44 characters in base64).

Example

Example DNS Record
REF1._workreferences.acmecorp.com.  3600  IN  TXT  "v=workreferences1; k=ed25519; p=Ky2hL9xR4bT...44chars...="

Registrar Examples

Namecheap
Host: REF1._workreferences
Value: v=workreferences1; k=ed25519; p=...
TTL: 3600
Cloudflare
Type: TXT
Name: REF1._workreferences
Content: v=workreferences1; k=ed25519; p=...
Key Rotation: To rotate keys, publish a new TXT record with a different selector (e.g. REF2). Keep old records active so previously-issued references remain verifiable.

3. Cryptographic Primitives

Ed25519 Digital Signatures

All references are signed using Ed25519 (Edwards-curve Digital Signature Algorithm on Curve25519). Ed25519 provides strong security guarantees with compact keys and fast operations.

FieldTypeDescription
AlgorithmEd25519Elliptic curve digital signature scheme (RFC 8032)
Public Key32 bytesBase64-encoded (44 characters). Published in DNS.
Private Key64 bytesBase64-encoded (88 characters). Contains seed + public key. Never leaves the client.
Signature64 bytesBase64-encoded (88 characters). Detached signature over the canonical payload.
EncodingBase64Standard base64 with padding (tweetnacl-util).
JavaScript - Key Generation
import nacl from 'tweetnacl';
import { encodeBase64 } from 'tweetnacl-util';

const keyPair = nacl.sign.keyPair();
const publicKey  = encodeBase64(keyPair.publicKey);   // 44 chars
const privateKey = encodeBase64(keyPair.secretKey);   // 88 chars

AES-256-GCM (Private Key Encryption)

Private keys are encrypted at rest using AES-256-GCM with a key derived from the user’s password. The server stores only the ciphertext and can never decrypt the private key without the password (zero-knowledge architecture).

FieldTypeDescription
CipherAES-256-GCMAuthenticated encryption (confidentiality + integrity).
Key DerivationPBKDF2SHA-256, 100,000 iterations.
Salt16 bytesRandom, base64-encoded. Unique per encryption.
IV / Nonce12 bytesRandom, base64-encoded. Recommended size for GCM.

SHA-256 Hashing

SHA-256 is used for two purposes: (1) as the hash function within PBKDF2 key derivation, and (2) for computing integrity hashes of reference payloads and PDF documents. Hashes are represented as lowercase hexadecimal strings.

4. Payload Format

The reference payload is a JSON object containing the reference data, a unique nonce, and the issuer’s domain and key selector. This makes each reference self-contained — a verifier can extract the payload and know exactly where to look up the public key via DNS.

JSON Structure

JSON
{
  "name":     "James Holloway",
  "role":     "Baggage Handler",
  "dates":    "June 2021 – December 2024",
  "text":     "James was a reliable and hardworking member of our airside baggage team...",
  "nonce":    "550e8400-e29b-41d4-a716-446655440000",
  "domain":   "heathrow-gh.com",
  "selector": "REF1"
}

Field Specifications

FieldTypeDescription
nameString (max 200)Full name of the candidate.
roleString (max 200)Job title or position held.
datesString (max 100)Employment period in free-form text (e.g. "Jan 2020 – Dec 2024").
textString (max 10,000)The reference letter content. Newlines are preserved.
nonceUUID v4Unique identifier. Prevents replay attacks and ensures signature uniqueness.
domainStringThe issuer’s domain (e.g. "acme.com"). Used for DNS public key lookup.
selectorStringDNS key selector (e.g. "REF1"). Maps to {selector}._workreferences.{domain}.

Canonical Serialisation

To ensure that both the signer and verifier produce identical byte representations, the payload is serialised with alphabetically sorted keys:

JavaScript - Canonical Form
function normalizeJson(obj) {
  return JSON.stringify(obj, Object.keys(obj).sort());
}

// Result (keys sorted alphabetically):
// {"dates":"June 2021...","domain":"heathrow-gh.com","name":"James Holloway","nonce":"550e8400...","role":"Baggage...","selector":"REF1","text":"James was..."}
Why sorting matters: JavaScript does not guarantee object key order. Without sorting, the same payload could produce different byte representations on different runtimes, causing signature verification to fail.

5. Signing Process

Signing happens entirely on the client side. The private key is decrypted in the browser using the user’s password, used to sign, and then discarded from memory. The plaintext private key is never sent to or stored on the server.

Steps

1. Compose reference data
name, role, dates, text, domain, selector
2. Generate nonce
crypto.randomUUID()
3. Canonical JSON
Sort keys, stringify
4. Encode to bytes
UTF-8 via TextEncoder
5. Ed25519 detached sign
nacl.sign.detached()
6. Base64-encode signature
64 bytes → 88 characters

Code Example

JavaScript - Signing
import nacl from 'tweetnacl';
import { encodeBase64, decodeBase64 } from 'tweetnacl-util';

function signReference(payload, privateKeyBase64) {
  // 1. Canonical serialisation (sorted keys)
  const canonical = JSON.stringify(payload, Object.keys(payload).sort());

  // 2. Encode to bytes
  const payloadBytes = new TextEncoder().encode(canonical);
  const privateKeyBytes = decodeBase64(privateKeyBase64);

  // 3. Sign (detached: signature does not contain the message)
  const signatureBytes = nacl.sign.detached(payloadBytes, privateKeyBytes);

  // 4. Return base64-encoded signature
  return encodeBase64(signatureBytes);  // 88 characters
}

6. Verification Process

Verification requires no central server or database. The reference document (PDF) contains everything needed: the signed payload, the signature, and the issuer’s domain and key selector. A verifier extracts this data, looks up the public key via DNS, and checks the signature. The result is one of three trust levels.

Algorithm

Extract payload + signature
From PDF metadata or QR code
Read domain and selector
From the payload itself
DNS TXT lookup
{selector}._workreferences.{domain}
Found ↓|Not found → SILVER
Verify Ed25519 signature
Canonical payload + DNS public key
Pass → GOLD|Fail → INVALID

Full Verification (Decentralised)

JavaScript - Complete Verification (Node.js)
import nacl from 'tweetnacl';
import { decodeBase64 } from 'tweetnacl-util';
import dns from 'dns/promises';

async function verifyReference(payload, signatureBase64) {
  // 1. Extract domain and selector from the payload itself
  const { domain, selector } = payload;

  // 2. Look up the public key via DNS
  const recordName = `${selector}._workreferences.${domain}`;
  let publicKeyBase64;
  try {
    const records = await dns.resolveTxt(recordName);
    const flat = records.map(r => r.join(''));
    const match = flat.find(r => r.startsWith('v=workreferences1;'));
    if (!match) return { status: 'silver', reason: 'No DNS record found' };
    publicKeyBase64 = match.match(/p=([A-Za-z0-9+/=]+)/)?.[1];
    if (!publicKeyBase64) return { status: 'silver', reason: 'Malformed DNS record' };
  } catch {
    return { status: 'silver', reason: 'DNS lookup failed' };
  }

  // 3. Verify the signature against the DNS public key
  const canonical = JSON.stringify(payload, Object.keys(payload).sort());
  const payloadBytes   = new TextEncoder().encode(canonical);
  const signatureBytes = decodeBase64(signatureBase64);
  const publicKeyBytes = decodeBase64(publicKeyBase64);

  const valid = nacl.sign.detached.verify(payloadBytes, signatureBytes, publicKeyBytes);
  return valid
    ? { status: 'gold', reason: 'Signature valid, DNS verified' }
    : { status: 'invalid', reason: 'Signature verification failed' };
}

7. Trust Levels

Verification produces one of three trust levels, determined by the combination of signature validity and DNS record presence.

Gold

Signature valid and DNS record found. Highest trust.

⚠️
Silver

Signature valid, DNS record not found. May indicate DNS misconfiguration or propagation delay.

Invalid

Signature failed, or reference is revoked or expired. Do not trust.

ConditionTrust Level
Signature valid + DNS record matchesGold
Signature valid + DNS record not foundSilver
Signature invalidInvalid
Reference revoked by issuerInvalid
Reference past expiry dateInvalid

8. PDF Proof Format

RefPassport generates a branded PDF document that serves as a portable proof of the reference. The PDF embeds all the data needed for verification in its metadata, and includes a QR code linking to the online verification page.

Visual Structure

Header: RefPassport branding
Reference Content
Candidate name, role, dates, reference text
Verification Footer
Signature hash, verification URL
QR
Footer bar

Embedded Metadata

The following data is embedded in the PDF’s document properties. Together, these fields contain everything needed for fully offline verification — no server or database required:

FieldTypeDescription
TitleString"Reference Letter - {candidateName}"
SubjectJSONThe full canonical JSON payload (includes domain and selector).
CreatorString"RefPassport"
Keywords[0]JSONFull canonical JSON payload (redundant copy).
Keywords[1]String"payload-hash:{SHA256_HEX}": SHA-256 hash of the canonical payload.
Keywords[2]String"signature:{BASE64}": the Ed25519 signature.

QR Code

Implementations MAY include a QR code linking to an online verification page for convenience. However, the PDF metadata is the authoritative source — the QR code is optional and the reference is verifiable without it.

Tamper Detection

The SHA-256 hash of the canonical payload is embedded in the PDF metadata as a keyword. A verifier can recompute the hash from the extracted payload and compare it against the embedded value to detect any metadata corruption.

9. Security Properties

Guarantees

Tamper-Evidence
Any modification to the payload invalidates the Ed25519 signature.
Non-Repudiation
Only the holder of the private key can produce valid signatures for a given public key.
Domain Binding
DNS TXT records link public keys to verified domains. An attacker cannot claim signatures from a domain they do not control.
Zero-Knowledge
The server stores only the encrypted private key. Without the user's password, the server cannot sign references.
Replay Protection
Each reference includes a UUID v4 nonce, ensuring unique signatures even for identical reference content.
Revocability
Issuers can revoke any reference at any time. Revoked references always return Invalid.

Threat Model

AttackDefence
Forged referenceEd25519 signature verification fails without the correct private key.
Modified contentAny change to the payload invalidates the detached signature.
Stolen private key from serverPrivate key is AES-256-GCM encrypted with user password. Server cannot decrypt.
DNS hijackingDegrades to Silver trust level. DNSSEC recommended for additional protection.
Replay attackUUID v4 nonce produces unique signatures for identical content.
PDF tamperingSHA-256 hash comparison detects any modification to the document.

Non-Goals

  • Anonymity: references are explicitly linked to identifiable domains.
  • Confidentiality: reference content is not encrypted in transit. Once shared, it is readable by any recipient.
  • Guaranteed availability: DNS lookup failures degrade to Silver, not Gold. Offline signature verification is possible.

10. Implementation Notes

For Issuers

  1. Generate an Ed25519 key pair on the client.
  2. Encrypt the private key with a strong password (PBKDF2, 100k iterations, AES-256-GCM).
  3. Publish the public key as a DNS TXT record at {SELECTOR}._workreferences.{DOMAIN}.
  4. Wait for DNS propagation (typically 15 minutes to 48 hours).
  5. Sign references client-side. Never send the plaintext private key to the server.
  6. Generate a PDF with the payload, signature, domain, and selector embedded in metadata.
  7. Share the verification link and/or PDF with the candidate.

For Verifiers

  1. Receive a PDF from the candidate.
  2. Extract the payload and signature from the PDF metadata.
  3. Look up the public key via DNS using the domain and selector from the payload.
  4. Verify the Ed25519 signature. Check the trust level: Gold (full trust), Silver (DNS not found), Invalid (signature failed).

Best Practices

Key Rotation
Use a new selector (e.g. REF2) for new keys. Keep old DNS records active so previously-issued references remain verifiable.
DNSSEC
Recommended but not required. Adds cryptographic verification to DNS responses, protecting against DNS hijacking.
Password Strength
Use a strong password for private key encryption (12+ characters, mixed case, digits, symbols).
DNS TTL
Set to 3600 seconds (1 hour) for a reasonable balance between caching and update speed.
Nonce Generation
Always use UUID v4 (crypto.randomUUID()) for the nonce. Never reuse nonces across references.

Recommended Libraries

FieldTypeDescription
tweetnaclJavaScriptEd25519 key generation, signing, and verification.
tweetnacl-utilJavaScriptBase64 encoding/decoding for keys and signatures.
pdf-libJavaScriptPDF generation with metadata embedding.
qrcodeJavaScriptQR code generation for verification URLs.
dns.promisesNode.jsDNS TXT record resolution for domain verification.
Web Crypto APIBrowser/NodeAES-256-GCM encryption and PBKDF2 key derivation.

Ready to implement?

Try the interactive demo to see the full signing and verification flow in action, or register your domain to start issuing references.