package pdfcert import ( "bytes" _ "embed" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "time" ) //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 Scope string VerifyURL string Channel string ByteLength int64 } // LookupURL builds a public VeraeTime verify deep link (same as explorer // scan-bar → /time, which auto-fills receipt/sha256 and runs Lookup). // https://explorer.verae.ai/time?receipt=…&sha256=… func LookupURL(sha256, receiptID string) string { sha256 = strings.TrimSpace(sha256) sha256 = strings.TrimPrefix(strings.ToLower(sha256), "0x") receiptID = strings.TrimSpace(receiptID) u := "https://explorer.verae.ai/time" sep := "?" if receiptID != "" { u += sep + "receipt=" + receiptID sep = "&" } if sha256 != "" { u += sep + "sha256=" + sha256 } return u } // Build returns the branded registration PDF using the same jsPDF layout as // Verae Timestamping.app / register.html export when Node is available. func Build(in Input) ([]byte, error) { created := in.CreatedAt if created == "" { // Do not invent a ledger time — leave blank for the PDF field. created = "" } scope := strings.ToUpper(strings.TrimSpace(in.Scope)) if scope == "" { scope = "GLOBAL" } 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)" } row := map[string]any{ "file": docName, "sha256": in.SHA256, "scope": scope, "receipt_id": in.ReceiptID, "created_at": created, "merkle": merkle, "lookup_url": lookup, "size_bytes": in.ByteLength, } if b, err := buildWithNode(row); err == nil && len(b) > 1000 { return b, nil } return buildFallbackGofpdf(in, docName, created, scope, merkle, lookup) } func buildWithNode(row map[string]any) ([]byte, error) { script := findBuildScript() if script == "" { return nil, fmt.Errorf("registration-pdf build.mjs not found") } tmp, err := os.MkdirTemp("", "verae-regpdf-*") if err != nil { return nil, err } defer os.RemoveAll(tmp) inPath := filepath.Join(tmp, "in.json") outPath := filepath.Join(tmp, "out.pdf") jb, err := json.Marshal(row) if err != nil { return nil, err } if err := os.WriteFile(inPath, jb, 0o600); err != nil { return nil, err } cmd := exec.Command("node", script, inPath, outPath) cmd.Dir = filepath.Dir(script) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return nil, fmt.Errorf("node build.mjs: %w (%s)", err, stderr.String()) } return os.ReadFile(outPath) } func findBuildScript() string { candidates := []string{ "/Library/Application Support/Verae/PrintStamp/registration-pdf/build.mjs", "/Users/marchon/research/PrintToVeraeTimeStamp/platform/registration-pdf/build.mjs", filepath.Join("platform", "registration-pdf", "build.mjs"), } // Walk up from executable if exe, err := os.Executable(); err == nil { dir := filepath.Dir(exe) candidates = append(candidates, filepath.Join(dir, "registration-pdf", "build.mjs"), filepath.Join(dir, "..", "registration-pdf", "build.mjs"), filepath.Join(dir, "platform", "registration-pdf", "build.mjs"), filepath.Join(dir, "..", "platform", "registration-pdf", "build.mjs"), ) } for _, c := range candidates { if st, err := os.Stat(c); err == nil && !st.IsDir() { abs, _ := filepath.Abs(c) return abs } } return "" } // Ensure created stays empty when unknown — used by tests. var _ = time.Now