./research / verifiable-audit-layer

שכבת ביקורת ניתנת לאימות עבור בינה מלאכותית.

שכבת ביקורת חושפת שינוי עבור הסקת מודלים והרצת סוכנים: סריאליזציה קנונית, גיבוב בהפרדת תחומים, חתימות DSSE, Merkle DAG לכל הרצה, ו-Merkle Mountain Range חוצה הרצות שמוכיח שאף הרצה לא נמחקה. כל פרימיטיב נבחר ממדד ביצועים ישיר מול החלופות האמיתיות, עם הנתונים הגולמיים מקובעים במאגר. הרשומה הזו היא התכנון והניסויים שמאחוריו.

ביקורתקריפטוגרפיהמדדי ביצועיםshipped

מודל האיום

אנו מניחים יריב השולט באחסון ויכול לשכתב רשומות בדיעבד, כולל המפעיל שיצר אותן. המטרה היא שכל עריכה של הרצה חתומה תהיה ניתנת לזיהוי, שהרשומה תישאר ניתנת לאימות לאורך תקופת שמירה הנמדדת בשנים, ושהאימות לא יחשוף שום קלט פרטי. שתי תכונות נשמרות נבדלות לכל אורך הדרך: השלמות (integrity) של הרצה בודדת, והמלאות (completeness) של קבוצת ההרצות. קבלה לכל הרצה נותנת את הראשונה ואינה אומרת דבר על השנייה, ולכן שתיהן מהונדסות בנפרד.

כל בחירה למטה נעשתה באותו אופן. איננו טוענים שפרימיטיב הוא הטוב ביותר; אנו משווים אותו לחלופות המהימנות על אותה מכונה ומקבעים את הנתונים הגולמיים, כך שכל מספר כאן משוחזר בפקודה אחת. הזמנים הם המהיר ביותר מכמה הרצות לאחר השלכת החימום, עם שמירת הממוצע וסטיית התקן כדי שמדידה לא יציבה תסומן במקום שתוסתר. המספרים מגיעים ממארח AMD64 יחיד על CPython 3.14 ובעלי משמעות כיחסים, לא כערכים מוחלטים.

ניסויים ותוצאות

כל אבן דרך הייתה מבחן ישיר מול החלופות האמיתיות, לא מדידה יחידה. הסקירה למטה; התוצאות המפורטות של כל אחת נמצאות בסעיפים שבהמשך.

אבן דרך נבחן מול תוצאה
צורה קנונית JSON נאיבי, CBOR JCS, זהה בית-בית, ~3x עלות
גיבוב SHA-256, BLAKE3, keccak-256 keccak מקורי 17x, אותם בתים
ליבה מקורית מנועי pip מול Rust חיתום 6x, שורש זהה בית-בית
חתימה ECDSA, BLS, ML-DSA Ed25519, 64 B, דטרמיניסטי
פוסט-קוונטי ML-DSA 44 / 65 / 87 בשלבים, אימות שווה, 38x גודל
עקבה לכל הרצה 8 מתקפות זיוף, תקורה כולן נתפסו, ~121 us / אירוע
יומן חוצה הרצות בנייה מחדש נאיבית של Merkle MMR, הוספה שטוחה, 483x

סריאליזציה קנונית

קפסולה חייבת להתגבב לאותם בתים בליבת ה-Python שלנו, בלקוח ה-TypeScript שלנו, ובכלי של המבקר עצמו, אחרת רשומה תקפה נכשלת באימות. אנו משתמשים ב-RFC 8785 (JSON Canonicalization Scheme), שמקבע את עיצוב המספרים, את סדר המפתחות לפי יחידת קוד UTF-16, ואת דחיית הערכים הלא-סופיים. בחנו אותו מול JSON עם מפתחות ממוינים נאיבית ומול CBOR.

גישה Python מול Node עלות
JSON ממוין נאיבי מתפצל על Unicode ומספרים בסיס
RFC 8785 JCS זהה בית-בית פי 3 בערך בסריאליזציה
CBOR בינארי, דורש מפענח לקריאה קומפקטי

JSON נאיבי מתפצל בין זמני ריצה על סדר Unicode ועיצוב מספרים, וכך שובר בשקט את האימות בין שפות. JCS זהה בית-בית בשניהם, בכ-3x זמן הסריאליזציה, משולם פעם אחת לכל גיבוב וזניח לצד הגיבוב ועבודת הרשת סביבו. אנו שומרים ממנו מימוש אחד בדיוק. כשהמודול המקורי שלנו הביא סריאלייזר JSON משלו, הסרנו אותו, כי מקנן שני שמסכים על קלטים רגילים ומתפצל על מקרי הקצה שיריב יכול לבחור גרוע מכלום.

גיבוב והליבה המקורית

עלים מגובבים ב-BLAKE3 וצמתי Merkle ב-keccak-256, שניהם בהפרדת תחומים לפי RFC 6962 (קידומות נבדלות לעלים ולצמתים פנימיים) כדי לסגור את מחלקת התמונה-הקדומה-השנייה שבה צומת פנימי מוצג כעלה. keccak-256 אינו SHA3-256; השניים חולקים תמורה אך נבדלים בבית ריפוד אחד, והמאמת שעל השרשרת מדבר keccak, כך שהחלפה שקטה תגרום לכל שורש להיכשל על השרשרת. שכבת הגיבוב לכן מחשבת את האלגוריתם המבוקש או זורקת שגיאה, לעולם לא כפיל, ורושמת את האלגוריתם בכל קפסולה כדי שמאמת ישחזר את הפונקציה המדויקת במקום להסיק אותה ממה שמותקן מקומית.

פרימיטיב, קלט 64 B תפוקה הערה
SHA-256 (stdlib) 1.50M ops/s בסיס, תמיד נוכח
BLAKE3 (pip) 1.44M ops/s שווה ל-SHA-256
keccak-256 (pip) 99k ops/s איטי פי 15, צוואר הבקבוק
keccak-256 (ליבת Rust) 1.69M ops/s מהיר פי 17 מ-pip

ל-keccak אין מימוש בספרייה הסטנדרטית, וזה מה שהפך את המודול המקורי לשווה בנייה. הפער רחב ביותר בקלטים בגודל אירוע כי עלות pycryptodome שם היא הקצאת אובייקטים לכל קריאה ולא גיבוב, בדיוק הגודל שעקבת ביקורת מגבבת הכי הרבה; מקצה לקצה, הליבה המקורית מעלה את חיתום הרצה של 1000 אירועים מ-78 ל-491 חיתומים לשנייה, בערך פי 6. מכריע: הפעלת המאיץ אינה יכולה לשנות את התוצאה: חיתום אותה הרצה עם ה-crate ובלעדיו נותן שורש זהה סיבית-סיבית, חבילת התאמה בודקת את שני המסלולים מול הווקטורים שפרסמו מחברי האלגוריתם, וה-CI בונה גלגל יחיד בעל ABI יציב (abi3) ומאמת את המוצר היחיד הזה על כל Python מ-3.10 עד 3.14. ההתקנה משנה את המהירות ותו לא.

חתימות לרשומה בת עשור

חתימה על רשומת ביקורת חייבת להחזיק כל עוד ניתן לערער על הרשומה, וחובות התיעוד של בינה מלאכותית נוטות כעת לעבר עשור. אם הסכימה נשברת בעוד הרשומה עדיין בחלון השמירה שלה, יריב יכול לזייף ולתארך אותה לאחור, כך שכל הארכיון נכשל בבת אחת. מדדנו Ed25519 מול ECDSA, BLS ומשפחת ה-NIST הפוסט-קוונטית ML-DSA, בתפוקה ובגדלים.

סכימה אימות (ops/s) גודל חתימה
ECDSA P-256 16 887 ~72 B
ML-DSA-44 (פוסט-קוונטי) 10 725 2420 B
Ed25519 10 375 64 B
BLS12-381 מימוש ייחוס בלבד 96 B, מצרף

שתי תוצאות הפכו הנחות שנשאנו עמנו, ואנו שומרים את שתיהן ברשומה. Ed25519 אינו המהיר ביותר לאימות: ECDSA P-256 נמדד מהיר בערך פי 1.6, על אסמבלי ספרייה מותאם. Ed25519 נשאר ברירת המחדל בגודל החתימה ובדטרמיניזם (RFC 8032), שמסיר את תקלת שימוש-חוזר-ב-nonce של ECDSA הדולפת מפתח פרטי בעוד החתימות עדיין מאומתות. וחתימות פוסט-קוונטיות אינן איטיות לאימות: ML-DSA-44 עקף מעט את Ed25519, כשעלותו בחתימה (איטי פי 13 בערך) ובגודל (בערך פי 38 מהבתים). מאחר שעקבה נחתמת פעם אחת ומאומתת שנים, הצד היקר הוא זה שאנו עושים הכי מעט. BLS נדחה על דרישה, לא על מספר: הצירוף מקפל חתימות רבות ל-96 בתים, אך צירוף מאומת הכול-או-כלום ודורש כל מפתח ומסר, מה שאינו מתיישב עם חשיפת ענף אחד של החלטה בלי לחשוף את השאר.

המסגור מחדש שחשוב: חתימות אינן חסויות, לכן "קצור עכשיו, פענח אחר כך" אינו חל, והאיום האמיתי הוא זיוף רטרואקטיבי בתוך חלון השמירה. עיגון שורש לפנקס ציבורי מוכיח מתי רשומה התקיימה ללא תלות בסכימת החתימה, כך שמפתח שבור עולה ביכולת להוכיח מי חתם רשומה אך לא מתי או מה. העיגון נמצא אפוא על הנתיב הקריטי ו-ML-DSA מדורג מאחוריו, כשמעטפת ה-DSSE נשמרת זריזת-אלגוריתם והאלגוריתם נרשם לכל קפסולה.

עקבת הביקורת לכל הרצה

כל הרצה היא רצף אירועים הוספה-בלבד ושרשור-בגיבוב (קריאות LLM, קריאות כלים, אחזורים, יצירת תת-סוכנים, אישורים אנושיים, החלטות מעקה), עם שורש Merkle מעל כולם וחתימת DSSE מעל השורש. נשמרים רק גיבובים של קלטים ופלטים, לעולם לא המטענים, כך שהעקבה שומרת פרטיות כברירת מחדל. המאמת אינו מחזיר רק תקף או לא תקף; הוא נוקב בשם הערובה שנשברה.

פעולה (הרצה של 102 אירועים) עלות התאמת קנה מידה
רישום אירוע אחד ~121 us שטוח, מתחת ל-0.01% מצעד
חיתום, בניית השורש 0.15 ms לינארי באירועים
אימות, חישוב מחדש של כל העלים 9.2 ms לינארי, בערך פי 60 מהחיתום

הרישום הוא פחות ממאית האחוז מצעד סוכן טיפוסי, הנשלט על ידי הלוך-ושוב אל המודל, כך שהתקורה אינה סיבה לדגום והעקבה נשארת מלאה במקום סטטיסטית. האימות כבד בהרבה מהחיתום כי הוא מחשב כל עלה מחדש מהתוכן בעוד החיתום בונה את העץ רק מעל גיבובים שכבר קיימים. אי-הסימטריה הזו נכונה לעקבת ביקורת, שבה הרישום קבוע והאימות נדיר, והיא מסמנת אימות-מחדש המוני כמקום שבו הליבה המקורית והמקביליות משתלמות בהמשך. כדי להמחיש את הערובות, הדגמה תוקפת הרצה חתומה בשמונה דרכים, כל אחת נדחית בסיבה מסוימת:

מתקפה נדחתה בתור
שכתוב פלט של מודל אי-התאמת גיבוב עלה
מחיקת אירוע או הוספתו אי-התאמת ספירת אירועים
שינוי סדר אירועים שבירת שרשרת
קטיעת ההרצה אי-התאמת ראש שרשרת
הורדת דרגת אלגוריתם הגיבוב אי-התאמת גיבוב עלה
הצגה תחת מפתח שגוי החתימה אינה מאומתת

יומן השקיפות חוצה ההרצות

קפסולה תקפה לכל הרצה אינה יכולה להוכיח שקבוצת ההרצות מלאה. מפעיל שמשמיט הרצה לא נוחה מחזיק אוסף קפסולות מושלמות כל אחת לחוד, והיסטוריה עם חור. אנו סוגרים זאת ביומן הוספה-בלבד יחיד של שורשי קפסולות התומך בהוכחות הכללה (הרצה נמצאת ביומן) ובהוכחות עקיבות (היומן בגודל מוקדם יותר הוא קידומת מדויקת של היומן כעת). העקיבות תופסת צנזורה: יומן שנבנה מחדש עם הרצה שהוסרה תקין מבנית מבפנים וכל קפסולה ששרדה עדיין מאומתת, אך הוא נכשל בבדיקת עקיבות מול השורש שפורסם קודם, ואותה בדיקה דוחה הוספה שתוארכה לאחור. זה עובד רק מול שורש שקובע לפני השיבוש, ולכן אותו שורש חייב לחיות היכן שהמפעיל אינו יכול לתקן, כגון עוגן ציבורי.

אנו משתמשים ב-Merkle Mountain Range במקום עץ Merkle פשוט כי הוספה רק מוסיפה צמתים ולעולם אינה משכתבת אף אחד, מה שמאפשר ליומן לחיות על אחסון כתיבה-פעם-אחת או אחסון אובייקטים ושומר על הוכחות ההכללה הישנות תקפות ככל שהיומן גדל. התכונה הזו כמעט אופטימלית, לא רק נוחה: ePrint 2025/234 מוכיח שכל התחייבות תמציתית של הוספה-בלבד חייבת לגרור מספר על-לינארי של עדכוני עדים, וגרסת MMR קרובה מגיעה בעצם לחסם.

גודל יומן בניית MMR בנייה מחדש פשוטה
500 1.3 ms 158 ms (120x)
1000 2.6 ms 618 ms (237x)
2000 5.2 ms 2491 ms (483x)

ההוספה נשארת שטוחה ככל שהיומן גדל (בערך 390k הוספות לשנייה גם ב-100 וגם ב-10,000 רשומות) בעוד בנייה מחדש של עץ פשוט צונחת מ-8176 ל-84 שורשים לשנייה על אותו טווח, החתימה הריבועית-מול-הלינארית-לוגריתמית. האחסון מתייצב על 2.00 גיבובים שמורים לרשומה והוכחות ההכללה גדלות רק מ-7 ל-17 צעדים בין 100 ל-100,000 רשומות. היכן שאנו מפסידים: הוכחות העקיבות שלנו הן O(log^2 n), נושאות מסלול לכל פסגה של היומן הישן במקום ה-O(log n) הבר-השגה, כמה קילובייטים ב-100,000 רשומות וחילוף מכוון עבור מבנה שסוקר יכול לבדוק בקריאה.

גבולות שאיננו חוצים

גיבוב נותן ראיה לשינוי, לא חסינות משינוי. יריב שבונה מחדש הרצה ומחשב מחדש את שורשה מייצר רשומה עקבית עם עצמה, כי שורש Merkle מוכיח שהאירועים תואמים לשורש, לא שהשורש הוא זה שפורסם. החתימה תופסת זאת, והעוגן הציבורי מוכיח את הזמן. יומן השקיפות נשען בגדול על אותה הנחה: הוא מזהה הרצה שנמחקה רק ביחס לשורש שקובע לפני המחיקה. אנו מצהירים על הגבולות האלה במוצר ובהדגמותיו, ובדיקה מאשרת שהדגמת הזיוף ממשיכה להראות את המתקפה שאיננו עוצרים, כדי שהמקרה הכן הזה לא יוכל להיעלם בשקט.

שחזוריות

כל מספר כאן מגיע ממדד ביצועים מקובע שרץ מחדש בפקודה אחת, לצד הבדיקות, חבילות ההתאמה בין שפות ובין מימושים, ומטריצת ה-CI המפעילה Python 3.10 עד 3.14 עם הליבה המקורית ובלעדיה. המתודולוגיה, ה-JSON הגולמי, והערות ההחלטה (כולל הממדים שבהם כל בחירה מפסידה) נמצאים במאגר, כי שכבה שהופכת בינה מלאכותית לניתנת לביקורת חייבת בעצמה להיות ניתנת לביקורת.