fix(pdfcert): branded Registration Document without mcp.verae.ai

Match the official Verae Timestamp Registration Document layout (logo,
frames, field list, QR). Disclaimer: only the digest was submitted.
Lookup/footer use explorer.verae.ai — no mcp.verae.ai or “via MCP”.
This commit is contained in:
George Lambert 2026-10-03 04:15:28 -04:00
commit 38b902d5e7
8 changed files with 279 additions and 82 deletions

BIN
assets/VeraeFullLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

5
go.mod
View file

@ -1,3 +1,8 @@
module github.com/VeraeLLC/PrintToVeraeTimeStamp module github.com/VeraeLLC/PrintToVeraeTimeStamp
go 1.26.1 go 1.26.1
require (
github.com/phpdave11/gofpdf v1.4.2 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
)

15
go.sum Normal file
View file

@ -0,0 +1,15 @@
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -127,20 +127,22 @@ func Run(opt Options) (Result, error) {
safeID := SanitizeReceiptID(rec.ReceiptID) safeID := SanitizeReceiptID(rec.ReceiptID)
certPath := filepath.Join(outDir, fmt.Sprintf("%s-verae-timestamp-%s.pdf", stem, safeID)) certPath := filepath.Join(outDir, fmt.Sprintf("%s-verae-timestamp-%s.pdf", stem, safeID))
pdfBytes := rec.PDF // Always prefer the branded Registration Document (digests-only wording,
if len(pdfBytes) == 0 { // no mcp.verae.ai). Edge attachPdf is optional and may still say MCP.
pdfBytes, err = pdfcert.Build(pdfcert.Input{ pdfBytes, err := pdfcert.Build(pdfcert.Input{
DocumentName: stem + ".pdf", DocumentName: stem + ".pdf",
SHA256: digest, SHA256: digest,
ReceiptID: rec.ReceiptID, ReceiptID: rec.ReceiptID,
CreatedAt: rec.CreatedAt, CreatedAt: rec.CreatedAt,
Merkle: rec.Merkle, Merkle: rec.Merkle,
Scope: "global",
Channel: string(opt.Channel), Channel: string(opt.Channel),
ByteLength: int64(len(srcBytes)),
}) })
if err != nil { if err != nil {
return Result{}, err return Result{}, err
} }
} _ = rec.PDF // upstream PDF ignored in favor of branded local certificate
if err := os.WriteFile(certPath, pdfBytes, 0o600); err != nil { if err := os.WriteFile(certPath, pdfBytes, 0o600); err != nil {
return Result{}, err return Result{}, err
} }

View file

@ -60,8 +60,14 @@ func TestRunZapierWritesNamedFiles(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
got, _ := os.ReadFile(res.CertificatePDF) got, _ := os.ReadFile(res.CertificatePDF)
if string(got) != string(pdf) { if !strings.HasPrefix(string(got), "%PDF") {
t.Fatalf("cert bytes mismatch") t.Fatal("certificate is not a PDF")
}
if !strings.Contains(string(got), "Verae Timestamp Registration Document") {
t.Fatal("missing branded title")
}
if strings.Contains(string(got), "mcp.verae.ai") || strings.Contains(string(got), "via MCP") {
t.Fatal("certificate must not mention mcp.verae.ai / via MCP")
} }
if _, err := os.Stat(res.ReceiptJSON); err != nil { if _, err := os.Stat(res.ReceiptJSON); err != nil {
t.Fatal(err) t.Fatal(err)

BIN
internal/pdfcert/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,103 +1,218 @@
package pdfcert package pdfcert
import ( import (
"bytes"
_ "embed"
"fmt" "fmt"
"os"
"path/filepath"
"strings" "strings"
"time" "time"
"github.com/phpdave11/gofpdf"
qrcode "github.com/skip2/go-qrcode"
) )
// Input for a simple one-page Verae registration certificate PDF. //go:embed logo.png
var embeddedLogo []byte
// Input for the official Verae Timestamp Registration Document.
type Input struct { type Input struct {
DocumentName string DocumentName string
SHA256 string SHA256 string
ReceiptID string ReceiptID string
CreatedAt string CreatedAt string
Merkle string Merkle string
VerifyURL string Scope string
VerifyURL string // optional override; must not be mcp.verae.ai
Channel string Channel string
ByteLength int64
} }
// Build returns a minimal PDF (no external deps) describing the stamp. // LookupURL builds a public lookup link without mcp.verae.ai.
func LookupURL(sha256, receiptID string) string {
sha256 = strings.TrimSpace(sha256)
receiptID = strings.TrimSpace(receiptID)
// Explorer is the public surface; include digest (+ receipt when present).
u := "https://explorer.verae.ai/?sha256=" + sha256
if receiptID != "" {
u += "&receipt=" + receiptID
}
return u
}
// Build returns the branded one-page registration PDF (US Letter).
func Build(in Input) ([]byte, error) { func Build(in Input) ([]byte, error) {
created := in.CreatedAt created := in.CreatedAt
if created == "" { if created == "" {
created = time.Now().UTC().Format(time.RFC3339) created = time.Now().UTC().Format(time.RFC3339Nano)
} }
local := "" scope := strings.ToUpper(strings.TrimSpace(in.Scope))
if t, err := time.Parse(time.RFC3339, created); err == nil { if scope == "" {
local = t.Local().Format(time.RFC1123) scope = "GLOBAL"
} }
verify := in.VerifyURL lookup := in.VerifyURL
if verify == "" && in.SHA256 != "" { if lookup == "" || strings.Contains(strings.ToLower(lookup), "mcp.verae.ai") {
verify = fmt.Sprintf("https://mcp.verae.ai/register.html?sha256=%s&receipt=%s", in.SHA256, in.ReceiptID) lookup = LookupURL(in.SHA256, in.ReceiptID)
}
merkle := in.Merkle
if merkle == "" {
merkle = "See ledger lookup via receipt CID"
}
docName := in.DocumentName
if docName == "" {
docName = "(digest only)"
} }
lines := []string{ pdf := gofpdf.New("P", "pt", "Letter", "")
"Verae Timestamp Registration", pdf.SetCompression(false) // keep certificate text extractable / greppable
"", pdf.SetMargins(48, 48, 48)
"Document: " + sanitize(in.DocumentName), pdf.SetAutoPageBreak(false, 0)
"SHA-256: " + in.SHA256, pdf.AddPage()
"Receipt ID: " + in.ReceiptID, pageW, pageH := pdf.GetPageSize()
"UTC: " + created, margin := 48.0
}
if local != "" {
lines = append(lines, "Local: "+local)
}
if in.Merkle != "" {
lines = append(lines, "Merkle/block: "+in.Merkle)
}
if in.Channel != "" {
lines = append(lines, "Channel: "+in.Channel)
}
lines = append(lines, "Verify: "+verify)
lines = append(lines, "", "Digests only — document bytes were not uploaded.")
return buildTextPDF(lines) // Outer frames (purple + gold) matching register.html / app.html
pdf.SetDrawColor(42, 24, 56)
pdf.SetLineWidth(2)
pdf.Rect(28, 28, pageW-56, pageH-56, "D")
pdf.SetDrawColor(196, 168, 90)
pdf.SetLineWidth(0.6)
pdf.Rect(34, 34, pageW-68, pageH-68, "D")
// Logo
logoPath, cleanup, err := materializeLogo()
if err == nil {
pdf.ImageOptions(logoPath, margin, 48, 120, 36, false, gofpdf.ImageOptions{ImageType: "PNG", ReadDpi: true}, 0, "")
if cleanup != nil {
defer cleanup()
}
} }
func sanitize(s string) string { pdf.SetTextColor(42, 24, 56)
s = strings.ReplaceAll(s, "(", "\\(") pdf.SetFont("Times", "B", 18)
s = strings.ReplaceAll(s, ")", "\\)") pdf.SetXY(margin, 100)
s = strings.ReplaceAll(s, "\\", "\\\\") pdf.CellFormat(pageW-margin*2, 20, "Verae Timestamp Registration Document", "", 0, "C", false, 0, "")
pdf.SetTextColor(90, 80, 100)
pdf.SetFont("Helvetica", "", 10)
pdf.SetXY(margin, 122)
pdf.CellFormat(pageW-margin*2, 12, "Official certificate of digital content registration on the VeraeTime global ledger", "", 0, "C", false, 0, "")
pdf.SetDrawColor(196, 168, 90)
pdf.SetLineWidth(1)
pdf.Line(margin, 142, pageW-margin, 142)
fields := [][2]string{
{"Document name", docName},
}
if in.ByteLength > 0 {
fields = append(fields, [2]string{"Document size", fmt.Sprintf("%d bytes", in.ByteLength)})
}
fields = append(fields,
[2]string{"Content SHA-256", in.SHA256},
[2]string{"Registration scope", scope},
[2]string{"Original timestamp (UTC)", created},
[2]string{"Receipt ID (CID)", orDash(in.ReceiptID)},
[2]string{"Merkle / block hash", merkle},
[2]string{"Ledger location", "VeraeTime global ledger - api.veraetime.net"},
[2]string{"Blockchain explorer", "https://explorer.verae.ai/"},
[2]string{"Public lookup URL", lookup},
)
y := 168.0
for _, fv := range fields {
label, value := fv[0], fv[1]
pdf.SetFont("Helvetica", "B", 10)
pdf.SetTextColor(42, 24, 56)
pdf.SetXY(margin, y)
pdf.Cell(pageW-margin*2, 12, label)
y += 14
pdf.SetFont("Helvetica", "", 10)
pdf.SetTextColor(40, 40, 50)
lines := pdf.SplitLines([]byte(value), pageW-margin*2-10)
for _, ln := range lines {
pdf.SetXY(margin, y)
pdf.Cell(pageW-margin*2-10, 12, string(ln))
y += 12
}
y += 10
if y > pageH-220 {
break
}
}
// QR code → explorer lookup (not mcp.verae.ai)
qrPNG, err := qrcode.Encode(lookup, qrcode.Medium, 280)
if err == nil {
qrFile, qclean, qerr := writeTempPNG(qrPNG)
if qerr == nil {
qrSize := 110.0
qrX := pageW - margin - qrSize
qrY := pageH - margin - qrSize - 36
pdf.ImageOptions(qrFile, qrX, qrY, qrSize, qrSize, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "")
pdf.SetFont("Helvetica", "", 8)
pdf.SetTextColor(90, 80, 100)
pdf.SetXY(qrX, qrY+qrSize+4)
pdf.CellFormat(qrSize, 10, "Scan to look up this registration", "", 0, "C", false, 0, "")
if qclean != nil {
defer qclean()
}
}
}
disclaimer := "This document certifies that the SHA-256 digest above was registered with Verae. File bytes were not uploaded; only the digest was submitted."
pdf.SetFont("Helvetica", "", 8)
pdf.SetTextColor(90, 80, 100)
pdf.SetXY(margin, pageH-64)
pdf.MultiCell(pageW-margin*2-130, 10, disclaimer, "", "L", false)
pdf.SetFont("Helvetica", "B", 9)
pdf.SetTextColor(42, 24, 56)
pdf.SetXY(margin, pageH-36)
pdf.Cell(pageW-margin*2, 12, "Verae | verae.com | explorer.verae.ai")
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func orDash(s string) string {
if strings.TrimSpace(s) == "" {
return "—"
}
return s return s
} }
// Minimal PDF 1.4 with Helvetica text lines. func materializeLogo() (path string, cleanup func(), err error) {
func buildTextPDF(lines []string) ([]byte, error) { // Prefer module assets path next to source; fall back to embed
var content strings.Builder candidates := []string{
content.WriteString("BT /F1 12 Tf 50 750 Td 14 TL\n") filepath.Join("assets", "VeraeFullLogo.png"),
for i, line := range lines { "/Users/marchon/research/PrintToVeraeTimeStamp/assets/VeraeFullLogo.png",
if i > 0 {
content.WriteString("T*\n")
} }
content.WriteString("(") for _, c := range candidates {
content.WriteString(sanitize(line)) if st, e := os.Stat(c); e == nil && st.Size() > 0 {
content.WriteString(") Tj\n") return c, nil, nil
} }
content.WriteString("ET\n") }
stream := content.String() if len(embeddedLogo) == 0 {
return "", nil, fmt.Errorf("logo not found")
objects := []string{ }
"<< /Type /Catalog /Pages 2 0 R >>", return writeTempPNG(embeddedLogo)
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
fmt.Sprintf("<< /Length %d >>\nstream\n%s\nendstream", len(stream), stream),
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
} }
var body strings.Builder func writeTempPNG(b []byte) (path string, cleanup func(), err error) {
body.WriteString("%PDF-1.4\n") f, err := os.CreateTemp("", "verae-logo-*.png")
offsets := make([]int, len(objects)+1) if err != nil {
for i, obj := range objects { return "", nil, err
offsets[i+1] = body.Len()
fmt.Fprintf(&body, "%d 0 obj\n%s\nendobj\n", i+1, obj)
} }
xrefStart := body.Len() if _, err := f.Write(b); err != nil {
fmt.Fprintf(&body, "xref\n0 %d\n", len(objects)+1) f.Close()
body.WriteString("0000000000 65535 f \n") os.Remove(f.Name())
for i := 1; i <= len(objects); i++ { return "", nil, err
fmt.Fprintf(&body, "%010d 00000 n \n", offsets[i])
} }
fmt.Fprintf(&body, "trailer << /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n", len(objects)+1, xrefStart) f.Close()
return []byte(body.String()), nil return f.Name(), func() { _ = os.Remove(f.Name()) }, nil
} }

View file

@ -0,0 +1,54 @@
package pdfcert
import (
"bytes"
"strings"
"testing"
)
func TestBuildBrandedRegistration(t *testing.T) {
b, err := Build(Input{
DocumentName: "Contract.pdf",
SHA256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
ReceiptID: "QmTestReceipt123",
CreatedAt: "2026-10-03T12:00:00Z",
Merkle: "113198",
Scope: "global",
ByteLength: 4096,
})
if err != nil {
t.Fatal(err)
}
if !bytes.HasPrefix(b, []byte("%PDF")) {
t.Fatal("not a pdf")
}
if len(b) < 5000 {
t.Fatalf("pdf too small (%d) — logo/qr missing?", len(b))
}
s := string(b)
if strings.Contains(s, "mcp.verae.ai") {
t.Fatal("must not mention mcp.verae.ai")
}
if strings.Contains(s, "via MCP") {
t.Fatal("must not say via MCP")
}
if !strings.Contains(s, "Verae Timestamp Registration Document") {
t.Fatal("missing title")
}
if !strings.Contains(s, "only the digest was submitted") {
t.Fatal("missing digests-only disclaimer")
}
if !strings.Contains(s, "explorer.verae.ai") {
t.Fatal("expected explorer lookup")
}
}
func TestLookupURLNoMCP(t *testing.T) {
u := LookupURL("abc", "rid")
if strings.Contains(u, "mcp.verae.ai") {
t.Fatal(u)
}
if !strings.Contains(u, "explorer.verae.ai") {
t.Fatal(u)
}
}