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}._refpassport.{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}._refpassport.{DOMAIN}
Type:  TXT
Value: v=refpassport1; k=ed25519; p={BASE64_PUBLIC_KEY}

Field Definitions

FieldTypeDescription
SELECTORStringAlphanumeric key selector (e.g. REF1, 2025Q1). Max 63 characters. Case-insensitive. Allows key rotation.
_refpassportFixedProtocol-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._refpassport.acmecorp.com.  3600  IN  TXT  "v=refpassport1; k=ed25519; p=Ky2hL9xR4bT...44chars...="

Registrar Examples

Namecheap
Host: REF1._refpassport
Value: v=refpassport1; k=ed25519; p=...
TTL: 3600
Cloudflare
Type: TXT
Name: REF1._refpassport
Content: v=refpassport1; 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 and a unique nonce. This is the exact data that gets signed.

JSON Structure

JSON
{
  "name":  "Sarah Chen",
  "role":  "Senior Software Engineer",
  "dates": "March 2021 – January 2025",
  "text":  "Sarah was an outstanding member of our engineering team...",
  "nonce": "550e8400-e29b-41d4-a716-446655440000"
}

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.

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":"March 2021...","name":"Sarah Chen","nonce":"550e8400...","role":"Senior...","text":"Sarah 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
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 is a multi-step process that checks the reference’s integrity, authenticity, and current status. The result is one of three trust levels.

Algorithm

Fetch reference by ID
Is it revoked?
Yes → INVALID|No ↓
Is it expired?
Yes → INVALID|No ↓
Verify Ed25519 signature
Canonical payload + public key
Pass ↓|Fail → INVALID
DNS TXT lookup
{SELECTOR}._refpassport.{DOMAIN}
Found → GOLD|Not found → SILVER

Signature Verification

JavaScript - Verification
import nacl from 'tweetnacl';
import { decodeBase64 } from 'tweetnacl-util';

function verifyReference(payload, signatureBase64, publicKeyBase64) {
  // 1. Reconstruct canonical form
  const canonical = JSON.stringify(payload, Object.keys(payload).sort());

  // 2. Decode inputs
  const payloadBytes   = new TextEncoder().encode(canonical);
  const signatureBytes = decodeBase64(signatureBase64);
  const publicKeyBytes = decodeBase64(publicKeyBase64);

  // 3. Verify
  return nacl.sign.detached.verify(payloadBytes, signatureBytes, publicKeyBytes);
}

DNS Verification

JavaScript - DNS Lookup (Node.js)
import dns from 'dns/promises';

async function verifyDns(domain, selector, expectedPublicKey) {
  const recordName = `${selector}._refpassport.${domain}`;
  const expectedValue = `v=refpassport1; k=ed25519; p=${expectedPublicKey}`;

  const records = await dns.resolveTxt(recordName);
  const flat = records.map(r => r.join('')); // TXT records may be multi-string

  return flat.some(record => record === expectedValue);
}

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 for machine-readable verification:

FieldTypeDescription
TitleString"Reference Letter - {candidateName}"
SubjectJSONThe full canonical JSON payload.
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.
Keywords[3]String"pdf-hash:{SHA256_HEX}": SHA-256 hash of the final PDF bytes.

QR Code

The QR code encodes the URL https://refpassport.com/verify/{REFERENCE_ID}. Scanning the QR code takes the verifier directly to the verification page.

Tamper Detection

The SHA-256 hash of the PDF bytes is stored in the database. A verifier can upload the PDF to check whether SHA256(uploadedPdf) === storedPdfHash. If the hashes differ, the PDF has been modified after issuance.

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}._refpassport.{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. Store the signature and payload in the database, generate a PDF with a QR code.
  7. Share the verification link and/or PDF with the candidate.

For Verifiers

  1. Receive a verification link or PDF from the candidate.
  2. Navigate to the verification page or perform programmatic verification.
  3. Check the trust level: Gold (full trust), Silver (partial), Invalid (reject).
  4. Optionally upload the PDF to verify its SHA-256 hash against the stored value.

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.