Skip to main content

Verification Web App

The verification web app is a lightweight React application served at verify.getveylent.com. Its sole purpose is to verify document signatures when a user scans the QR code on a signed PDF.

Design Goals​

  • Zero friction -- No app install, no account, no login
  • Mobile first -- Optimized for phone screens (QR scanning)
  • Client-side decryption -- The encryption key never leaves the browser
  • Fast -- Minimal bundle size, single API call

How It Works​

URL Routing​

HTTPS://VERIFY.GETVEYLENT.COM/V/<anchor ID, 40 digits>#<key, 40 digits>
└────── path ────────┘ └── fragment ──┘

The React Router matches /v/:anchorB64 (case-insensitively, so /V/ too) and extracts both the path parameter and the URL fragment. Earlier links carried base64url parts instead of digits; both decode. See QR Code.

Verification Sequence​

The page calls verifyAnchor from libs/shared/crypto. The mobile app (apps/verify) runs the same function.

QR code ──> Browser: URL with anchor ID + key fragment
Browser: decode anchor ID (40 digits or base64url -> 0x hex, 16 bytes)
Browser ──> API: GET /api/verify/0x<anchorId>
API ──> Chain: anchorBlock(anchorId), then DocumentAnchored event in that block,
then previousAnchorId back to the first signature
API ──> Browser: on-chain record + chain + encrypted payload
Browser: AES-128-GCM decrypt(key, payload)
Browser: require SHA-256(decrypted JSON) == compositeHash
and record.a == anchor ID
Browser: display signer info

The server is not trusted on its own. The record must decrypt with the key from the QR code, hash to the composite hash on the blockchain, and name the same anchor ID. Otherwise the page shows a mismatch and hides the details.

Security: The Fragment Stays Private​

The #fragment portion of a URL is never sent to the server in HTTP requests. This is a fundamental property of URLs defined in RFC 3986. This means:

  • The API server never sees the decryption key
  • Network logs and proxies never capture the key
  • Only the browser (and by extension, the user) has access to the key

Technology​

AspectChoice
FrameworkReact 19
BundlerVite
CryptoWeb Crypto API (crypto.subtle)
StylingInline styles (no CSS framework)
HostingAny static file host

Web Crypto API Requirement​

The Web Crypto API requires a secure context (HTTPS). The verification page will not work over plain HTTP. In production, this is handled by serving from an HTTPS domain. For local development, a self-signed certificate is used via @vitejs/plugin-basic-ssl.

Verification States​

The page displays one of these states:

Verified (Green)​

The record decrypts, matches the on-chain composite hash, and names this anchor ID. Shows:

  • Signer name, email, type, company, position, trust anchor
  • Document hash, signing timestamp, geolocation
  • Anchor ID, transaction hash, block time, chain length

The page notes that it can't check a copy of the document. That requires the desktop app.

Partial Verification (Yellow)​

The blockchain anchor exists but the URL fragment (decryption key) is missing. This can happen if:

  • The QR was manually typed without the fragment
  • The URL was shared without the fragment

Shows: anchor ID, transaction hash, block time, chain length.

Doesn't Match the Blockchain (Red)​

The record doesn't open with the key, doesn't hash to the on-chain composite hash, or belongs to another anchor ID. The details are not shown, because they can't be trusted.

No Such Signature (Red)​

No anchor with this ID exists on the blockchain. The QR code may be damaged or not made by Veylent.

Couldn't Verify (Red)​

The link is malformed, or the API couldn't be reached or returned an error. Shows the error message.

Bundle Size​

The verification app is intentionally minimal:

  • No PDF rendering library
  • No state management library
  • No CSS framework
  • Total bundle: ~70 KB gzipped (mostly React)