הצינור: מקובץ בדפדפן לחתימה שאי אפשר לזייף
רוב העיבוד רץ על המכשיר עם pdf-lib, ומעליו שכבת SEAL שחותמת ומאמתת בלי לסמוך על שרת.
החוכמה היא ש-SEAL לא שומר את הקובץ בשום מקום אלא רק hash- מתחייב עליו: חותמים על keccak256 של ה-PDF בתוך התחייבות EIP-712 חתומה ב-secp256k1 (MetaMask אם יש ארנק, אחרת מפתח fallback מקומי), ומקבלים .seal.json + QR נפרדים. עמוד האימות מחשב מחדש את ה-hash מהקובץ שהועלה ומשחזר את כתובת החותם מהחתימה - כך שכל שינוי של בית אחד בקובץ מפרק את ההתאמה ומחזיר 'אימות נכשל', בלי צורך לסמוך על אף שרת. הצד השני של הליבה הוא ה'אפייה': כל פעולת עורך - סיבוב, מחיקת עמודים, שינוי סדר, אנוטציה - נצרבת דרך pdf-lib ישירות לתוך הקובץ הסופי, כדי שאף עריכה לא תיעלם בשקט בין המסך לבייטים שנחתמים.
כל תא הוא פעולת עורך אחת מתוך 31 ב-OP_LABELS: 23 המוארות רצות לגמרי על המכשיר דרך pdf-lib ו-pdf.js, 6 המרות Office מנותבות לשרת המרה חיצוני (רדום - ממתין למפתח), ו-2 stubs מוצהרים.
מה זה
Click PDF הוא סוויטת כלים ל-PDF שרובה עובדת בצד הלקוח: העלאה, עריכה, מיזוג, פיצול, סיבוב, חיתוך, מחיקת עמודים, מספור, סימני מים, חתימה ויזואלית ועוד. יש בו 31 פעולות עורך, ו-23 מהן רצות לגמרי בדפדפן דרך pdf-lib ו-pdf.js. היוצאות מן הכלל הן 6 המרות Office, שמתוכננות לעבור דרך שרת המרה חיצוני (CloudConvert) - נתיב שממתין למפתח ולכן רדום כרגע - ועוד 2 פעולות שהן stubs מוצהרים.
הממשק כולו בעברית RTL, mobile-first, ומכוון לרמת החלקות של smallpdf. האתר בנוי מ-27 עמודי HTML בלי שלב build - vanilla HTML/CSS/JS.
הבעיה
רוב כלי ה-PDF ברשת מעלים את הקובץ לשרת זר. זו בעיית פרטיות, וגם אין דרך אמיתית להוכיח אחר כך שהקובץ שיצא הוא בדיוק מה שנחתם ולא שונה בדרך.
רציתי כלי שהעיבוד בו נשאר על המכשיר, ושמאפשר לחתום על מסמך בצורה שכל אחד יכול לאמת - בלי לסמוך על שרת.
מה בניתי
עורך PDF מלא שרץ בדפדפן, שבו כל פעולה - כולל סיבוב, מחיקת עמודים, שינוי סדר וחתימות שהונחו - נאפית לתוך הקובץ הסופי שנשמר או נחתם. השקעתי הרבה בכך שאף פעולה לא 'תיעלם בשקט' בזמן שמירה.
מעליו בניתי את SEAL: שכבת חתימה דיגיטלית. חותמים על קובץ עם keccak256 והתחייבות EIP-712 חתומה ב-secp256k1 (דרך MetaMask אם יש ארנק, אחרת מפתח fallback מקומי), ומקבלים קובץ .seal.json + QR. עמוד האימות מחשב מחדש את ה-hash ומשחזר את החותם, ומחזיר פסק דין: 'אותנטי' או 'אימות נכשל'.
איך זה עובד
הפרונטאנד הסטטי מתארח ב-Vercel ומגיש CSP קשיח בלי inline scripts. כל ספריות ה-PDF וה-crypto מתארחות אצלי (self-hosted) עם SRI, ו-cdnjs הוסר לגמרי מה-CSP. ה-script host הצד-שלישי היחיד שנשאר הוא Turnstile של Cloudflare, שנטען בעצלתיים רק בפעולת המרה.
בשרת יש Cloudflare Worker שמטפל ב-signup/login/me/contact/convert ובהורדות חתומות, מעל D1 (משתמשים, sessions, הודעות), KV ל-rate limiting, R2 ו-Turnstile. יש גם anti-clone gate בקצה, עם allowlist שמאפשר לקראולרים של חיפוש ורשתות חברתיות לקבל את העמוד האמיתי.
הסטאק
Vanilla HTML/CSS/JS בלי bundler, pdf-lib ו-pdf.js לעיבוד ה-PDF, ethers עם EIP-712 ל-SEAL. Cloudflare Workers + D1 + KV + R2 + Turnstile בצד השרת, פריסה על Vercel.
החתימה על שרשרת (phase-1b) בנויה כשלד מלא ב-Foundry עם חוזה SealRegistry.sol שעבר audit אדוורסרי, ומחכה רק לחיבור ארנק/RPC/gas כדי לעלות.
התוצאה
המערכת חיה ב-click-pdf.vercel.app ואומתה מקצה לקצה בדפדפן אמיתי: טעינת PDF, הנחת אנוטציה, שמירה עם העריכה אפויה בפנים, ומחזור SEAL מלא - חתימה, ואז אימות שמחזיר 'אותנטי' על הקובץ הנכון ו'אימות נכשל' על עותק ששונה.
מה שנשאר בהמתנה הוא dressing תפעולי בלבד: מפתח CloudConvert להמרות Office, Stripe, שליחת מייל ודומיין ייעודי. הליבה - עריכה וחתימה - עובדת.