From 38b902d5e74ea832a84ea8a6f46e5c7749dc09bf Mon Sep 17 00:00:00 2001 From: George Lambert Date: Sat, 3 Oct 2026 04:15:28 -0400 Subject: [PATCH] fix(pdfcert): branded Registration Document without mcp.verae.ai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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”. --- assets/VeraeFullLogo.png | Bin 0 -> 27484 bytes go.mod | 5 + go.sum | 15 ++ internal/job/job.go | 28 ++-- internal/job/job_test.go | 10 +- internal/pdfcert/logo.png | Bin 0 -> 27484 bytes internal/pdfcert/pdfcert.go | 247 ++++++++++++++++++++++--------- internal/pdfcert/pdfcert_test.go | 54 +++++++ 8 files changed, 278 insertions(+), 81 deletions(-) create mode 100644 assets/VeraeFullLogo.png create mode 100644 go.sum create mode 100644 internal/pdfcert/logo.png create mode 100644 internal/pdfcert/pdfcert_test.go diff --git a/assets/VeraeFullLogo.png b/assets/VeraeFullLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..4c011e8bd1fe92faea2df0c4c69b8369cc3326bc GIT binary patch literal 27484 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 literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 8727af7..77c9992 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module github.com/VeraeLLC/PrintToVeraeTimeStamp go 1.26.1 + +require ( + github.com/phpdave11/gofpdf v1.4.2 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ec56742 --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/job/job.go b/internal/job/job.go index 31e93b2..4034178 100644 --- a/internal/job/job.go +++ b/internal/job/job.go @@ -127,20 +127,22 @@ func Run(opt Options) (Result, error) { safeID := SanitizeReceiptID(rec.ReceiptID) certPath := filepath.Join(outDir, fmt.Sprintf("%s-verae-timestamp-%s.pdf", stem, safeID)) - pdfBytes := rec.PDF - if len(pdfBytes) == 0 { - pdfBytes, err = pdfcert.Build(pdfcert.Input{ - DocumentName: stem + ".pdf", - SHA256: digest, - ReceiptID: rec.ReceiptID, - CreatedAt: rec.CreatedAt, - Merkle: rec.Merkle, - Channel: string(opt.Channel), - }) - if err != nil { - return Result{}, err - } + // Always prefer the branded Registration Document (digests-only wording, + // no mcp.verae.ai). Edge attachPdf is optional and may still say MCP. + pdfBytes, err := pdfcert.Build(pdfcert.Input{ + DocumentName: stem + ".pdf", + SHA256: digest, + ReceiptID: rec.ReceiptID, + CreatedAt: rec.CreatedAt, + Merkle: rec.Merkle, + Scope: "global", + Channel: string(opt.Channel), + ByteLength: int64(len(srcBytes)), + }) + if err != nil { + return Result{}, err } + _ = rec.PDF // upstream PDF ignored in favor of branded local certificate if err := os.WriteFile(certPath, pdfBytes, 0o600); err != nil { return Result{}, err } diff --git a/internal/job/job_test.go b/internal/job/job_test.go index 1dec275..2012b2e 100644 --- a/internal/job/job_test.go +++ b/internal/job/job_test.go @@ -60,8 +60,14 @@ func TestRunZapierWritesNamedFiles(t *testing.T) { t.Fatal(err) } got, _ := os.ReadFile(res.CertificatePDF) - if string(got) != string(pdf) { - t.Fatalf("cert bytes mismatch") + if !strings.HasPrefix(string(got), "%PDF") { + 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 { t.Fatal(err) diff --git a/internal/pdfcert/logo.png b/internal/pdfcert/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4c011e8bd1fe92faea2df0c4c69b8369cc3326bc GIT binary patch literal 27484 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 literal 0 HcmV?d00001 diff --git a/internal/pdfcert/pdfcert.go b/internal/pdfcert/pdfcert.go index ec5f23f..5d99749 100644 --- a/internal/pdfcert/pdfcert.go +++ b/internal/pdfcert/pdfcert.go @@ -1,103 +1,218 @@ package pdfcert import ( + "bytes" + _ "embed" "fmt" + "os" + "path/filepath" "strings" "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 { DocumentName string SHA256 string ReceiptID string CreatedAt string Merkle string - VerifyURL string + Scope string + VerifyURL string // optional override; must not be mcp.verae.ai 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) { created := in.CreatedAt if created == "" { - created = time.Now().UTC().Format(time.RFC3339) + created = time.Now().UTC().Format(time.RFC3339Nano) } - local := "" - if t, err := time.Parse(time.RFC3339, created); err == nil { - local = t.Local().Format(time.RFC1123) + scope := strings.ToUpper(strings.TrimSpace(in.Scope)) + if scope == "" { + scope = "GLOBAL" } - verify := in.VerifyURL - if verify == "" && in.SHA256 != "" { - verify = fmt.Sprintf("https://mcp.verae.ai/register.html?sha256=%s&receipt=%s", in.SHA256, in.ReceiptID) + lookup := in.VerifyURL + if lookup == "" || strings.Contains(strings.ToLower(lookup), "mcp.verae.ai") { + 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{ - "Verae Timestamp Registration", - "", - "Document: " + sanitize(in.DocumentName), - "SHA-256: " + in.SHA256, - "Receipt ID: " + in.ReceiptID, - "UTC: " + created, - } - 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.") + pdf := gofpdf.New("P", "pt", "Letter", "") + pdf.SetCompression(false) // keep certificate text extractable / greppable + pdf.SetMargins(48, 48, 48) + pdf.SetAutoPageBreak(false, 0) + pdf.AddPage() + pageW, pageH := pdf.GetPageSize() + margin := 48.0 - 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() + } + } + + pdf.SetTextColor(42, 24, 56) + pdf.SetFont("Times", "B", 18) + pdf.SetXY(margin, 100) + 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 sanitize(s string) string { - s = strings.ReplaceAll(s, "(", "\\(") - s = strings.ReplaceAll(s, ")", "\\)") - s = strings.ReplaceAll(s, "\\", "\\\\") +func orDash(s string) string { + if strings.TrimSpace(s) == "" { + return "—" + } return s } -// Minimal PDF 1.4 with Helvetica text lines. -func buildTextPDF(lines []string) ([]byte, error) { - var content strings.Builder - content.WriteString("BT /F1 12 Tf 50 750 Td 14 TL\n") - for i, line := range lines { - if i > 0 { - content.WriteString("T*\n") +func materializeLogo() (path string, cleanup func(), err error) { + // Prefer module assets path next to source; fall back to embed + candidates := []string{ + filepath.Join("assets", "VeraeFullLogo.png"), + "/Users/marchon/research/PrintToVeraeTimeStamp/assets/VeraeFullLogo.png", + } + for _, c := range candidates { + if st, e := os.Stat(c); e == nil && st.Size() > 0 { + return c, nil, nil } - content.WriteString("(") - content.WriteString(sanitize(line)) - content.WriteString(") Tj\n") } - content.WriteString("ET\n") - stream := content.String() - - objects := []string{ - "<< /Type /Catalog /Pages 2 0 R >>", - "<< /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 >>", + if len(embeddedLogo) == 0 { + return "", nil, fmt.Errorf("logo not found") } + return writeTempPNG(embeddedLogo) +} - var body strings.Builder - body.WriteString("%PDF-1.4\n") - offsets := make([]int, len(objects)+1) - for i, obj := range objects { - offsets[i+1] = body.Len() - fmt.Fprintf(&body, "%d 0 obj\n%s\nendobj\n", i+1, obj) +func writeTempPNG(b []byte) (path string, cleanup func(), err error) { + f, err := os.CreateTemp("", "verae-logo-*.png") + if err != nil { + return "", nil, err } - xrefStart := body.Len() - fmt.Fprintf(&body, "xref\n0 %d\n", len(objects)+1) - body.WriteString("0000000000 65535 f \n") - for i := 1; i <= len(objects); i++ { - fmt.Fprintf(&body, "%010d 00000 n \n", offsets[i]) + if _, err := f.Write(b); err != nil { + f.Close() + os.Remove(f.Name()) + return "", nil, err } - fmt.Fprintf(&body, "trailer << /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n", len(objects)+1, xrefStart) - return []byte(body.String()), nil + f.Close() + return f.Name(), func() { _ = os.Remove(f.Name()) }, nil } diff --git a/internal/pdfcert/pdfcert_test.go b/internal/pdfcert/pdfcert_test.go new file mode 100644 index 0000000..d1254d2 --- /dev/null +++ b/internal/pdfcert/pdfcert_test.go @@ -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) + } +}