Skip to main content

QR Code

Veylent embeds a QR code alongside each signature block. The QR encodes a verification URL that links the physical document to its blockchain anchor and carries the key to its sealed signing record.

URL Format​

HTTPS://VERIFY.GETVEYLENT.COM/V/<anchor ID, 40 digits>#<key, 40 digits>
└──── path param ────┘ └── fragment ──┘
SegmentEncodingSizePurpose
anchorId40 decimal digits16 bytesIdentifies the signature on the blockchain
key40 decimal digits16 bytesAES-128-GCM key that opens the sealed signing record

The #fragment is never sent to any server (RFC 3986). The decryption key stays entirely client-side.

The anchor ID and key are random values generated before anything is stamped. That is what allows the QR code to be placed on the page in the same layer as the signature, and to be covered by the document hash. The base URL is set at build time with VEYLENT_QR_BASE_URL, so self-hosted deployments encode their own verification site.

Digit Encoding​

Each 16-byte value is split into two big-endian 8-byte halves, and each half is written as a zero-padded 20-digit decimal number (u64::MAX is 20 digits). Fixed width keeps leading zero bytes, and a verifier rejects any part that isn't exactly 40 digits or whose halves don't fit in 64 bits.

The whole link is upper case. Scheme and host are case-insensitive, and every verifier accepts /V/ and /v/.

Why Digits and Upper Case​

A QR code can switch between encoding modes inside one code, and two of them are much denser than plain bytes:

ModeCharactersBits per character
Numeric0-93.33
Alphanumeric0-9 A-Z $%*+-./: and space5.5
Byteanything8

The digits go in numeric mode, and the upper-case HTTPS://VERIFY.GETVEYLENT.COM/V/ goes in alphanumeric mode. Only the # needs byte mode. Numeric mode stores the 32 bytes of anchor ID and key in about 267 bits, close to the 256-bit minimum, so tighter packing gains nothing. What remains is the base URL.

Example URL Breakdown​

HTTPS://VERIFY.GETVEYLENT.COM/V/0008198552921648689500081985529216486895#0108510259257115009501085102592571150095
├── base URL (29) ──────────┤ ├── anchor ID (40) ───────────────────┤ ├── key (40) ─────────────────────────┤

Total URL length: 113 characters, which the QR code stores in about 505 bits.

Links from earlier builds used lower case and base64url parts: https://verify.getveylent.com/v/<base64url(anchorId)>#<base64url(key)>. Verifiers still accept them. A 40-digit part and a 22-character base64url part can't be confused.

Version & Error Correction​

QR codes have 40 versions (1--40) and 4 error correction levels. Higher versions hold more data but produce denser codes. Higher EC levels tolerate more damage but reduce capacity.

Veylent uses EC level M (15% damage tolerance, suitable for printed documents) and lets the qrcode crate pick the smallest version that fits:

QrCode::with_error_correction_level(url.as_bytes(), EcLevel::M)
LinkEC levelVersionModules
Digits, upper case (current)MV433x33
Digits, lower-case URLMV537x37
Base64url, lower case (earlier)MV641x41

V4/M holds 64 data codewords (512 bits), so the default link has a few bits to spare. A longer self-hosted base URL moves up to V5 (37x37) automatically.

Quiet Zone​

The QR specification requires a 4-module quiet zone (blank padding) around the QR pattern. The qrcode crate includes this in the rendered image, so the fraction depends on the version:

let quiet_zone_frac = 4.0 / (code.width() + 8) as f32; // V4: 4 / 41 ≈ 9.8%
let quiet_zone = qr_size * quiet_zone_frac;
let inner_qr = qr_size - 2.0 * quiet_zone;

This positions the branding text against the QR pattern itself. The signature placer's preview uses the V4 fraction, 4/41.

Minimum Size​

The QR code, quiet zone included, is rendered at a minimum of 34 PDF points (~12mm).

Image modulesMin size (pt)Pattern sizeModule sizePixels at 300 DPI
41 (V4 + quiet zone)34~9.7mm0.83 pt (~0.29mm)~3.5 px/module

Phone cameras read modules of about 0.33mm reliably, and printed copies that may be photocopied are safer at 0.4mm or more. The 34pt minimum is below both, so short signature blocks give the smallest QR codes the least margin.

Rendering​

The QR image is rendered as a grayscale (DeviceGray) PDF XObject:

  1. Generate QR using the qrcode crate at 570x570 pixels (high resolution for clean scaling)
  2. Flip horizontally (compensates for PDF coordinate system interactions)
  3. Compress with zlib (FlateDecode)
  4. Add as a single XObject, referenced by all placement content streams

Branding Text​

Below the QR code, "Signed with Veylent" is rendered in light gray Helvetica, right-aligned with the QR pattern edge (accounting for quiet zone offset).