Skip to main content

Manual verification

This page describes the offline verification algorithm in full. It is the authoritative reference for anyone who wants to verify a Doc E Sign document without using the online verification page.

The algorithm requires only the signed PDF. No Doc E Sign account, no internet connection, no external tools beyond a SHA-256 implementation.

Note on the "Signing chain fingerprint" label

The Signing chain fingerprint printed on the audit page is not a direct SHA-256 hash of the signed PDF. It is the final value in a hash chain computed across the complete signing process. Step 5 of the algorithm below shows exactly how it is derived.

What this algorithm does and does not establish

A match in step 6 confirms the printed values on the audit page are internally self-consistent — nothing has been altered since sealing. It does not, on its own, confirm that the document was genuinely produced by Doc E Sign, because the calculation requires no secret: anyone could construct a self-consistent set of numbers independently, without ever using Doc E Sign. Confirming genuine provenance requires online verification.


What you need

  • The signed PDF
  • A SHA-256 implementation (available in every major programming language and as a command-line tool on all operating systems)

The algorithm

1. From the audit page, read pre_audit_byte_length (integer).
2. Extract bytes [0 .. pre_audit_byte_length − 1] from the PDF → signed_pdf_bytes.
3. Compute: sealed_pdf_hash = lowercase_hex(SHA-256(signed_pdf_bytes))
4. Set H = Original document fingerprint (from the audit page).
5. For each event in the event log (in the order listed on the audit page):
If event_type is "completed":
payload = sealed_pdf_hash (computed in step 3 — do not use the value from the audit page)
Otherwise:
payload = the payload value as printed in the audit page event log
H = lowercase_hex(SHA-256(H + "|" + event_type + "|" + timestamp + "|" + payload))
6. Assert H equals the Signing chain fingerprint printed on the audit page.

If the assertion in step 6 passes, the document is unaltered and the event log is complete and correct.

If it fails, either the PDF or the audit page has been modified since signing completed.


Field formats

FieldFormat
pre_audit_byte_lengthInteger; printed on the audit page
Event timestampISO 8601 UTC, no fractional seconds — e.g. 2026-05-30T14:22:05Z
Event payloadEither a lowercase hex string, or the literal value none
Hash valuesLowercase hex SHA-256 — 64 characters

The | separator in step 5 is a literal ASCII pipe character (byte 0x7C). Concatenation uses no encoding — raw string concatenation.


What verification proves

  • Step 3 independently verifies that the pre-audit-page PDF bytes match the sealed document. If the signed PDF has been altered since signing, sealed_pdf_hash will differ from what the chain expects, and the final assertion will fail.
  • Step 5 verifies that the event log is complete, in order, and unaltered. Changing any event's type, timestamp, or payload — or inserting or removing an event — produces a different Signing chain fingerprint.

What it does not prove

  • That the chain is genuinely Doc E Sign's. The algorithm requires no secret — it only checks that the printed numbers are mutually consistent. It cannot distinguish a document genuinely sealed by Doc E Sign from a self-consistent forgery constructed without ever using Doc E Sign. Confirming genuine provenance requires online verification.
  • The underlying identity data behind HMAC payloads. Identity HMAC payloads (for link_clicked and signed events) are pre-computed values stored on the audit page. The chain verifies their inclusion and order but does not reveal the underlying data (IP address, user agent). Independently verifying that a specific HMAC corresponds to specific data requires Doc E Sign's HMAC secret or the online verification endpoint.

Reference implementations

JavaScript (Node.js built-in crypto)

const { createHash } = require('crypto');
const { readFileSync } = require('fs');

function verify(pdfPath, auditPage) {
const pdfBytes = readFileSync(pdfPath);

// Step 2–3: extract pre-audit bytes and hash them
const preAuditBytes = pdfBytes.slice(0, auditPage.preAuditByteLength);
const sealedPdfHash = createHash('sha256').update(preAuditBytes).digest('hex');

// Step 4: start with Hash 1
let h = auditPage.originalDocumentFingerprint;

// Step 5: replay the chain
for (const event of auditPage.events) {
const payload = event.type === 'completed' ? sealedPdfHash : event.payload;
const input = `${h}|${event.type}|${event.timestamp}|${payload}`;
h = createHash('sha256').update(input).digest('hex');
}

// Step 6: assert
const valid = h === auditPage.signingChainFingerprint;
console.log(valid ? 'VALID' : 'INVALID');
return valid;
}

Python (hashlib)

import hashlib

def verify(pdf_path: str, audit_page: dict) -> bool:
with open(pdf_path, 'rb') as f:
pdf_bytes = f.read()

# Steps 2–3: extract pre-audit bytes and hash them
pre_audit_bytes = pdf_bytes[:audit_page['pre_audit_byte_length']]
sealed_pdf_hash = hashlib.sha256(pre_audit_bytes).hexdigest()

# Step 4: start with Hash 1
h = audit_page['original_document_fingerprint']

# Step 5: replay the chain
for event in audit_page['events']:
payload = sealed_pdf_hash if event['type'] == 'completed' else event['payload']
input_str = f"{h}|{event['type']}|{event['timestamp']}|{payload}"
h = hashlib.sha256(input_str.encode()).hexdigest()

# Step 6: assert
valid = h == audit_page['signing_chain_fingerprint']
print('VALID' if valid else 'INVALID')
return valid

Shell (sha256sum / shasum)

This shell script demonstrates the algorithm for a single-event chain. For a real document with multiple events, loop over the event log entries from the audit page.

macOS

Replace sha256sum with shasum -a 256 on macOS. The output format is identical — cut -d' ' -f1 extracts the hex either way.

#!/bin/bash
set -euo pipefail

PDF="$1"
PRE_AUDIT_LENGTH="$2" # pre_audit_byte_length from the audit page
HASH1="$3" # Original document fingerprint
EVENT_TYPE="$4" # e.g. "completed"
EVENT_TIMESTAMP="$5" # e.g. "2026-05-30T14:22:05Z"
EVENT_PAYLOAD="$6" # payload from audit page (or "none")
EXPECTED_HASH2="$7" # Signing chain fingerprint

# Steps 2–3: hash the pre-audit bytes
SEALED_PDF_HASH=$(dd if="$PDF" bs=1 count="$PRE_AUDIT_LENGTH" 2>/dev/null | sha256sum | cut -d' ' -f1)

# For "completed" events, use the sealed PDF hash as payload
if [ "$EVENT_TYPE" = "completed" ]; then
PAYLOAD="$SEALED_PDF_HASH"
else
PAYLOAD="$EVENT_PAYLOAD"
fi

# Step 5: compute H[i]
CHAIN_INPUT="${HASH1}|${EVENT_TYPE}|${EVENT_TIMESTAMP}|${PAYLOAD}"
COMPUTED=$(printf '%s' "$CHAIN_INPUT" | sha256sum | cut -d' ' -f1)

# Step 6: compare
if [ "$COMPUTED" = "$EXPECTED_HASH2" ]; then
echo "VALID"
else
echo "INVALID"
echo "Expected: $EXPECTED_HASH2"
echo "Computed: $COMPUTED"
exit 1
fi

Online verification

The recommended way to verify a document is to upload the signed PDF at doc-e-sign.com/verify — the fingerprint is read automatically from the embedded QR code. Manual fingerprint entry is available on that page as a fallback if the QR code cannot be read.

Online verification does more than replay the algorithm above. It looks up the Signing chain fingerprint in Doc E Sign's own database, confirming the chain was genuinely produced by Doc E Sign rather than merely self-consistent, and independently re-derives the identity HMAC payloads using a secret only Doc E Sign holds (see "What it does not prove," above). It also compares the uploaded file's actual bytes against the sealed document hash on record — an independent check the offline algorithm cannot perform, since offline verification trusts the PDF's own audit page for that value. On success it returns the signing context (date, document title, signer email domain).

Offline verification remains valuable on its own terms: it works without any dependency on Doc E Sign's continued existence, and it is what a court or auditor can use to confirm a document independently, years later, with no online lookup required. But offline and online verification answer different questions — a passing offline check confirms the document is internally self-consistent; a passing online check additionally confirms it is genuinely Doc E Sign's.