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

תורת הקבוצות היא אחד מהנושאים הבסיסיים שלומדים סטודנטים למתמטיקה בימינו. המושג המרכזי שבו היא עוסקת הוא מושג האינסוף – ולא אינסוף פילוסופי אבסטרקטי "בלתי מושג" ו"בלתי מובן", אלא אינסוף מאוד קונקרטי ומתמטי; כל כך קונקרטי, שניתן לסווג גדלים אינסופיים למספר סוגים שונים של גדלים; יש אינסוף "קטן" ויש אינסוף "גדול יותר", ולמעשה יש אינסוף גדלים שונים של אינסוף. האינסוף הקטן ביותר הוא גודלה של קבוצת המספרים הטבעיים – למספרם של המספרים הטבעיים קוראים \(\aleph_0\) (ממש האות העברית אלף – זו האות שבה בחר קנטור, ממציא תורת הקבוצות, להשתמש). לעומת זאת, אפשר להראות שגודל קבוצת המספרים הממשיים הוא אינסוף שהוא "גדול ממש" מ-\(\aleph_0\) (ההוכחה נקראת "האלכסון של קנטור" וכבר הראיתי אותה כאן), וגודל זה מסומן בתור \(\aleph\) (בלי אפס). ואז צצה השאלה המעניינת – האם יש גודל אינסופי שהוא "בין" \(\aleph_0\) ובין \(\aleph\)? גדול ממש מהאחד, אבל קטן ממש מהשני? הסברה הרווחת בזמנו של קנטור, כאשר השאלה עלתה, היא שלא קיים גודל שכזה, וסברה זו נקראה "השערת הרצף".

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

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

ראשית, חייבים להבהיר מה המשמעות של המושגים הבסיסיים שאני מדבר עליהם בפוסט הזה – מה זו "תורה"? מהן "אקסיומות"? ומה זו "הוכחה"?

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

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

כדי לתאר טענות צריך להשתמש בשפה כלשהי. במתמטיקה לא משתמשים בעברית או באנגלית אלא בשפות פורמליות, שמכילות סימנים עבור משתנים, קבועים, פונקציות ויחסים. למשל, \(x,y\) יכולים להיות משתנים, \(<\) הוא סימן יחס, ו-\(x<y\) היא כבר טענה של ממש, שבה טוענים שבין הערך של המשתנה \(x\) והערך של המשתנה \(y\) יש את היחס ">". מה שמעניין כאן הוא שלא ברור את מה המשתנים מייצגים בעצם – האם אלו מספרים? ואולי אלו דווקא עצים בינאריים? או מילים בשפה העברית? או בני אדם? ומה הסימן ">" מייצג? האם זה "קטן מ-", או שיש לו משמעות אחרת?

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

חזרה לשפה. יש הרבה סוגים שונים של שפות שאפשר לדבר עליהן, אבל כל מה שאנחנו נתעסק בו הוא מה שנקרא "שפה מסדר ראשון". בשפה כזו אפשר לטעון טענות "אטומיות" על משתנים וקבועים (\(x<y\) היא דוגמה לטענה כזו; אבל גם "\(x\) זוגי" היא טענה לגיטימית, כל עוד מתייחסים ל"זוגי" בתור שם כלשהו לתכונה ולא מייחסים לה, בשלב זה, משמעות כלשהי), וכמו כן ניתן לטעון טענות "מכומתות" שלא מדברות על איבר ספציפי אלא על איבר כלשהו מבין כל האיברים הקיימים, כמו "כל \(x\) הוא זוגי", או "קיים \(x\) שהוא זוגי". ה"סדר ראשון" שיש בשם השפה מעיד על כך שאפשר לכמת רק איברים בודדים; בשפה מסדר שני אפשר לטעון גם טענות כמו "אם לכל קבוצה של \(x\)-ים מתקיים כך וכך אז קיימת קבוצה של \(x\)-ים שמקיימת כך וכך", אבל כאמור, לא על זה נדבר כאן. מי שלא ממש הבין על מה דיברתי בפסקה הזו, לא נורא; זה לא הכרחי כדי להבין את הרעיון הכללי, אם כי זה קריטי למי שמנסה ללמוד את הדברים הללו בצורה מסודרת.

כעת אפשר לתאר תורות. כל תורה מוגדרת מעל שפה כלשהי, ומכילה אוסף של אקסיומות שמנוסחות בלשון אותה שפה. למשל, אם בשפה שלנו יש את התכונה "זוגי" ואת הפונקציה "+", אז אפשר להכניס לתורה את האקסיומה "\(x\) זוגי אם ורק אם קיים \(y\) כך ש-\(x=y+y\)". זו דוגמה לטענה שניתן לנסח בשפה, גם באופן פורמלי לחלוטין: \(A(x)\leftrightarrow \exists y(x=y+y)\), כאשר אנחנו משתמשים ב-\(A(x)\) בתור ייצוג סימבולי של היחס "\(x\) זוגי". העובדה שהאקסיומה הזו היא חלק מהתורה שלנו גורמת ליחס "זוגי", שעד כה היה חסר משמעות לגמרי, לקבל משמעות כלשהי – מעתה, \(x\) הוא זוגי אם ורק אם קיים \(y\) כך ש-\(x=y+y\). אלא שמשמעות הטענה האחרונה הזו לא ברורה עד הסוף, כי לא ברור מה המשמעות של "=" ושל "+" כאן – תחת פרשנויות שונות של הסימנים הללו, גם המשמעות של "\(x\) זוגי" תשתנה.

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

השפה של תורת החבורות כוללת משתנים (שמסמנים בדרך כלל ב-\(x,y,z,\dots\)), קבוע \(e\) שבא לייצג איבר מיוחד, סימן פונקציה יחיד, שבא לייצג פעולה בין שני איברים בחבורה (כשתוצאת הפעולה היא עצמה איבר בחבורה) ומסומן בנקודה (כמו פעולת "כפל"), וביחס בודד – שוויון. בכל תורה שיש בה את יחס השוויון יש אקסיומות שנותנות לפעולת השוויון משמעות הדומה לזו האינטואיטיבית (איבר יכול להיות שווה רק לעצמו, למשל) אבל לא נציג אותן כאן כדי לא לסבך, אלא נתמקד רק באקסיומות המיוחדות של תורת החבורות, ומה הן?

  1. \(\forall x,y,z(x\cdot(y\cdot z)=(x\cdot y)\cdot z)\) ("אסוציאטיביות")
  2. \(\forall y(e\cdot y=y \wedge y\cdot e=y)\) ("איבר יחידה")
  3. \(\forall x\exists y(x\cdot y=e \wedge y\cdot x=e)\) ("קיום הופכי")

אם כן, מה האקסיומות אומרות? הראשונה אומרת שכאשר "כופלים" שלושה איברים (מעתה והלאה אקרא לפעולה "כפל" גם בלי מרכאות, אבל כפי שנראה בהמשך, היא לא חייבת לייצג את פעולת הכפל הרגילה) אין חשיבות לשאלה האם קודם כל נכפול את \(x\) ב-\(y\) ורק את התוצאה נכפול ב-\(z\), או שמא קודם כל נכפול את \(y\) ב-\(z\) ורק אחר כך נכפול את \(x\) בתוצאה.

האקסיומה השניה אומרת שהאיבר המיוחד שסומן באות \(e\) הוא בעל התכונה שכל איבר שמוכפל בו (הן מימין והן משמאל) לא משתנה. האקסיומה השלישית אומרת שלכל איבר קיים איבר "הופכי" כך שהמכפלה של שניהם (ושוב, לא משנה מי מימין ומי משמאל) מחזירה את האיבר המיוחד \(e\).

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

אבל אין מה לעשות. אם לוקחים שלושה מספרים שלמים \(x,y,z\) רואים שמתקיים \(x+(y+z)=(x+y)+z\) (איך רואים? איך מוכיחים את זה? זו שאלה לדיון נפרד). כמו כן, המספר 0 הוא בעל התכונה המעניינת ש-\(x+0=0+x=x\), ולכל מספר שלם \(x\) קיים מספר \(-x\) כך ש-\(x+(-x)=(-x)+x=0\). אם כן, זה נראה כמו חבורה, וההבדל היחיד הוא בסימונים – במקום \(\cdot\) אנחנו משתמשים בסימן +, ובמקום \(e\) אנחנו משתמשים בסימון \(0\). אם כן, המספרים השלמים הם פרשנות אפשרית אחת לתורת החבורות, פרשנות שבה ה"עולם" שאיברים ממנו מיוצגים בידי משתנים הוא אוסף המספרים השלמים, הסימן \(e\) מפורש בתור המספר אפס, והסימון \(\cdot\) מפורש בתור פעולת החיבור. שימו לב מה אני עושה כאן – אני מתאים סימון, שהוא בסך הכל אוסף של תווים, למשהו בעל משמעות, כמו פעולה חשבונית, או מספר.

זו פרשנות אפשרית אחת. פרשנות אפשרית אחרת היא מטריצות הפיכות מסדר 2 מעל הממשיים, למשל. מי שאינו מכיר מטריצות, לא נורא; די לדעת שמדובר במעין טבלאות של מספרים, ושמוגדרת עליהן פעולת כפל מחוכמת למדי; הפעולה מקיימת את כל האקסיומות של חבורה, אבל בנוסף אם \(A,B\) הן מטריצות אז ייתכן שיתקיים \(A\cdot B\ne B\cdot A\), כלומר בכל פעולת כפל יש חשיבות לשאלה מי מימין ומי משמאל (שימו לב שזה שונה מתכונת ה"אין חשיבות לסדר" של האסוציאטיביות, כי באסוציאטיביות לא מחליפים פעולה של כפל מימין בפעולה של כפל משמאל).

אם כן, ניתן לפרש את תורת החבורות גם כאילו האיברים שלה הן מטריצות הפיכות מסדר 2 מעל הממשיים, הסימן \(\cdot\) מייצג פעולה של כפל מטריצות, והסימן \(e\) מייצג את מטריצת היחידה (מטריצה שיש אחדות באלכסון שלה, וכל שאר הכניסות שלה הן 0). די ברור שזוהי פרשנות שונה מהותית מאשר הפרשנות של "מספרים שלמים"; לא רק בגלל שהאובייקטים שבשתי הפרשנויות שונים (מטריצות אל מול מספרים), אלא בעיקר בגלל שיש טענות שנכונות רק בפרשנות אחת, ולא בפרשנות אחרת – בפרט, הטענה \(\forall x,y(x\cdot y=y\cdot x)\)  (שבניסוח מילולי אומרת "החבורה היא אבלית") נכונה עבור שלמים אך לא נכונה עבור מטריצות.

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

בשביל מה כל המשחק הזה טוב? המטרה הסופית שלנו בבניית תורות היא להוכיח בהן משפטים. הנה דוגמה למשפט שאפשר להוכיח בתורת החבורות: "איבר היחידה הוא יחיד", ובניסוח פורמלי: \(\forall x(\forall y(x\cdot y=y \wedge y\cdot x=y))\rightarrow x=e\). כלומר, אם \(x\) כלשהו מקיים את התכונה שמגדירה את איבר היחידה, הוא בהכרח שווה לו. קל למדי להוכיח את המשפט הזה רק מתוך האקסיומות שהצגתי לעיל – נסו זאת בתור תרגיל (ההוכחה שלכם לא תהיה פורמלית לגמרי, כי לא אמרתי במפורש מהם כללי ההיסק, אבל אני מניח שהרעיון יהיה ברור). כעת ניתן לקטוף את הפירות – אם הוכחנו משפט כלשהו בתורה שלנו, נובע מכך שהוא נכון לכל המודלים של התורה הזו. כלומר, גם בשלמים וגם במטריצות, איבר היחידה יהיה יחיד. לתכונה הזו – אם בתורה ניתן להוכיח משהו אז הוא נכון בכל מודל של התורה – קוראים "נאותות" של התורה. התכונה הזו כלל לא טריוויאלית – אם נבחר כללי היסק "עקומים", ייתכן שהיא פשוט לא תתקיים. יש צורך להראות שהיא מתקיימת עבור שפות מסדר ראשון עם כללי ההיסק הרגילים (הדבר הזה מכונה "משפט הנאותות"), אבל מן הסתם לא אכנס לכך כעת.

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

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

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

אם כן, איך מגיעים מ"לכל תורה עקבית יש מודל" ל"כל טענה שנכונה בכל מודל של התורה, יכיחה ממנה"? פשוט מאוד. נניח שיש לנו תורה ויש טענה \(\varphi\) שנכונה בכל מודל של התורה אך אינה יכיחה ממנה; כעת לוקחים את התורה שלנו ומוסיפים לה אקסיומה בודדת – השלילה של הטענה \(\varphi\). התוצאה שמתקבל היא תורה שהיא עדיין עקבית (זה נובע מכך שלא ניתן להוכיח את \(\varphi\)) ולכן קיים לה מודל; אבל על פי משפט הנאותות שהזכרתי למעלה, כל משפט שניתן להוכיח מהתורה מתקיים באותו המודל, כלומר יש לנו מודל שבו הטענה \(\varphi\) דווקא לא מתקיימת – סתירה.

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

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

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

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

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

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

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

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

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

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

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

האם ניתן להבחין בזמן פולינומי בין ההגדרה הפורמלית של מחולל פסאודו אקראי לזו המעשית?

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

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

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

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

הבה נעבור כעת למספרים פסאודו אקראיים. הזכרתי בפוסט הקודם כמה שימושים בסיסיים של מספרים שכאלו. השאלה המרכזית שעולה, כשמשתמשים במספרים פסאודו אקראיים ליישום כלשהו, היא "האם העובדה שאני משתמש במספרים פסאודו אקראיים ולא במספרים אקראיים באמת משנה משהו?".

ובכן, קשה לתת הגדרה פורמלית ל"משהו" הזה. זו אמרה ידועה שהיעד הנכסף ביותר של המתמטיקאי אינו משפט טוב כי אם הגדרה טובה. הגישה שאתאר בפוסט הזה מנסה לפרמל את ה"משהו" הזה באמצעות משהו דמוי ה"מבחין" שתיארתי לעיל. נניח כי אני מהנדס תוכנה שקיבל לידיו "קופסה שחורה" של מחולל פסאודו אקראי שאני רוצה לבדוק את איכותו, וכמו כן יש לי גישה למחולל אקראי אמיתי (נניח, כזה שמבוסס על פיזיקה). מה שאעשה יהיה לכתוב תוכנית שלפעמים עונה 1 ולפעמים עונה 0, כתוצאה של חישוב אקראי כלשהו, ולבדוק אמפירית (על ידי זה שאריץ אותה פעמים רבות) מה ההסתברות שהיא תחזיר 1. אם אגלה שההסתברות הזו שונה כאשר האקראיות של התוכנית נובעת ממחולל "אמיתי", לעומת ההסתברות הזו כשהתוכנית משתמש במחולל הפסאודו אקראי, פירוש הדבר הוא שהמחולל הפסאודו אקראי איננו מוצלח – הנה, מצאנו תוכנית (ה"מבחין" שלי) שעבורה השימוש במחולל פסאודו אקראי באמת משנה משהו.

כדי להבין את הרעיון שלעיל הכרחי לתת דוגמה. אם כן, נניח שהמחולל שאני מנסה לבדוק הוא המחולל המופלא של xkcd מהפוסט הקודם שמחזיר רק 4, בעוד שמחולל אמיתי צריך להחזיר את הערכים בין 1 ל-6 בהסתברות אחידה. מה תהיה התוכנית שאכתוב? פשוט מאוד – תוכנית שמגרילה מספר בעזרת המחולל, פולטת 1 אם קיבלה 4, ו-0 אם קיבלה משהו אחר. מה הולך כאן?

נניח שהתוכנית משתמשת במחולל האקראי האמיתי. מה ההסתברות שלה לפלוט 1? כמובן, 1/6. לכן אם אריץ אותה הרבה פעמים, אספור כמה פעמים קיבלתי 1 ואחלק במספר ההרצות הכולל, אקבל מספר שקרוב ל-1/6. לעומת זאת, אם התוכנית משתמשת במחולל של xkcd, היא תמיד תחזיר 1, ולכן כשאספור כמה פעמים קיבלתי 1 ואחלק במספר ההרצות הכולל, אקבל 1. ההבדלים כאן ברורים בצורה שאינה משתמעת לשתי פנים.

השאלה המתבקשת כאן היא "איך ידעת לכתוב דווקא את התוכנה הזו ולא, נניח, תוכנה שפולטת 1 אם היא הגרילה 3 או 2?". מן הסתם התוכנית שכתבתי "תפורה" על המחולל הפסאודו אקראי של xkcd, במיוחד כדי להראות בעיה שיש בו (אם כי, שימו לב שהוא מחולל כה גרוע עד שגם התוכנית השניה הזו "תפיל" אותו). עבור מחוללים מסובכים יותר כלל לא ברור שאדע לכתוב תוכנית שיוצרת הבחנה בינו ובין התפלגות אקראית אמיתית; אבל זה שאני לא יודע לכתוב כזו לא אומר שלא קיימת תוכנית כזו. וזו בדיוק הדרישה ממחולל פסאודו אקראי "מתמטי" – שלא תהיה קיימת תוכנית שכזו.

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

אם כן, הסברתי כיצד קובעים שמחולל פסאודו אקראי הוא מוצלח – ובקריפטוגרפיה תיאורטית, כדי שמשהו ייקרא "מחולל פסאודו אקראי" הוא צריך להיות מוצלח – אבל טרם הסברתי מה זה בעצם, מבחינה פורמלית, מחולל שכזה. ובכן, זה אלגוריתם דטרמיניסטי שמקבל קלט, ובונה ממנו פלט ארוך יותר, כך שאם הקלט התפלג באופן אחיד, גם הפלט אמור להיראות כאילו הוא מתפלג באופן אחיד, במובן שהוסבר לעיל. כאשר "משתמשים" במחולל הזה, מניחים שיש מקור של אקראיות "אמיתית" שמספק למחולל הפסאודו אקראי את הקלט הקצר – כאמור, בעולם האמיתי אפשר להשתמש בהתקנים פיזיקליים, בשעון המחשב ובדברים דומים לשם כך. מה שחשוב במחולל הפסאודו אקראי הוא ה"ניפוח" שהוא יוצר. איך זה מתקשר למחוללים אמיתיים, שכל הפלטים שהם מוציאים הם מאותו אורך כמו הגרעין הנוכחי? פשוט מאוד – אנחנו חושבים על הפלט הארוך של המחולל הפסאודו אקראי התיאורטי בתור כל הסדרה האקראית שנוצרת מהגרעין. כלומר, מה שמקבלים כשקוראים לפונקציה rand שוב, ושוב, ושוב, עד שהיא מתחילה לחזור על עצמה. כלומר, גם על מחוללים "אמיתיים" ניתן לחשוב כעל יצורים שמבצעים "ניפוח" של קלט אקראי קצר.

בואו ניקח דוגמה ממש אווילית. נניח שאני מעביר למחולל קלט בן סיבית אחת – או 0, או 1. נניח שעל 0 הוא מחזיר 11 ועל 1 הוא מחזיר 01. זה אומר שיש לו בדיוק שני פלטים אפשריים מגודל 2, ובפרט שהפלט 00, למשל, לא יופיע אף פעם. זו הבעיה הבסיסית של מחולל פסאודו אקראי – מכיוון שהוא מבצע "ניפוח", ולכל קלט אפשרי יש פלט אפשרי יחיד, הוא לא יכול לכסות את כל מרחב הפלטים האפשריים, ולכן אף פעם לא ייראה אקראי "לגמרי". תמיד אפשר יהיה להריץ אותו על כל הקלטים האפשריים ולראות איזה ערך של פלט אף פעם לא יכול להתקבל, ואז להשתמש בכך כדי להראות שההתפלגות של המחולל היא לא אחידה "באמת" כי היא פוסחת על ערכים. על כן, מעמידים את המחולל במבחן רק עבור ערכים גדולים מספיק של קלט שעבורם לא ניתן לבצע חיפוש ממצה על כל מרחב הקלטים (ההגדרות הפורמליות קצת יותר עדינות ומחוכמות, כמובן; אבל יהיה קשה להסביר אותן כאן בלי הכנה מוקדמת נוספת).

אחת התוצאות המדהימות הראשונות הנוגעות למחוללים פסאודו אקראיים היא שדי במחולל שכל מה שהוא עושה הוא לנפח את הקלט בביט אחד (כלומר, אם הוא מקבל n ביטים כקלט, הוא מוציא n+1 ביטים כפלט – ביטים שיכולים להיראות שונה לגמרי מהקלט שהתקבל) כדי שניתן יהיה לבנות מחולל עם ניפוח פולינומי כלשהו (נניח, מוציא פלט שגדול פי 5 מהקלט). התוצאה הזו מצמצמת מאוד את ה"אתגר" שבבניית מחולל פסאודו אקראי – כל מה שצריך הוא ניפוח שמגדיל בביט אחד. עם זאת, זה לא עזר – עד היום לא ידוע ולו על מחולל פסאודו אקראי אחד. כמובן, קיימים מחוללים רבים שבהם משתמשים בפועל, גם ביישומים קריפטוגרפיים רגישים; אבל אין הוכחה לכך שהם אכן עונים על ההגדרה הפורמלית, ולכן שאלת קיומו של מחולל פסאודו אקראי נותרה בגדר תעלומה.

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

הסיבה לכך היא שאם P=NP, ניתן תמיד לבנות מבחין, עבור כל מחולל פסאודו אקראי, שפועל כמעט באופן זהה למבחין שבניתי עבור המחולל האווילי של xkcd. האבחנה הראשונה היא שתוכנית שמוציאה 1 על כל קלט שיכול להיווצר על ידי המחולל הפסאודו אקראי, ומוציאה 0 על קלטים אחרים, תמיד פולטת 1 כשהיא מופעלת על המחולל הפסאודו אקראי, אבל פולטת 1 בהסתברות שהיא לכל היותר חצי כאשר היא מופעלת על התפלגות אקראית "אמיתית". הסיבה לכך היא, כמו שראינו כבר, שמחולל פסאודו אקראי מכסה לכל היותר חצי ממרחב הערכים האקראיים האפשריים (זה במקרה שבו הניפוח שלו הוא של ביט בודד; עבור ניפוח גדול יותר הוא מכסה עוד פחות). מכאן שתוכנית שכזו היא המבחין האולטימטיבי. השאלה היא רק מה זמן הריצה של תוכנית שכזו. כמובן, זה תלוי בצורה שבה התוכנית מבצעת את החישוב שלה.

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

כאן נכנסת לתמונה שאלת P=NP. כזכור, NP ניתנת לתיאור כאוסף כל שאלות הכן/לא שאולי קשה לפתור, אבל בהינתן "הוכחה" קצרה לנכונות טענה כלשהי, קל לבדוק אותה. זה מתאים בדיוק לתרחיש הנוכחי, כאשר השאלה היא "האם x יכל להתקבל כפלט של המחולל?" וה"הוכחה" היא פשוט הערך שיש להזין למחולל כדי לקבל את x (החישוב שמבצע המחולל הוא יעיל, כך שהבדיקה ניתנת לביצוע ביעילות). כעת, אם P=NP, נובע מכך שכל שאלת כן/לא שמקיימת את התכונה "קל לבדוק הוכחות", מקיימת גם את התכונה "קל לפתור". כלומר, בהינתן x יהיה קל (מבחינה חישובית) לקבוע האם הוא יכול להתקבל כפלט של המחולל או לא. איך? שאלה טובה – זה מה שהוכחה ש-P=NP תצטרך להסביר – אבל אפשר. ואם אפשר, אז קיים מבחין פולינומי עבור המחולל, ולכן הוא לא עונה להגדרה הפורמלית. סוף הסיפור.

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

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

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

למה זה עובד? שוב, מבלי להוכיח פורמלית אלא רק לתת נימוק על קצה המזלג – כי אם היה אפשר לשבור את ההצפנה הזו בצורה יותר מוצלחת מאשר אפשר היה לשבור הצפנה שבה המפתח מוגרל לכל הודעה בנפרד (להבדיל מכך שהוא נוצר על ידי מחולל פסאודו אקראי), הרי שזה היה מספק לנו "מבחין" עבור המחולל הפסאודו אקראי. נניח שיש אלגוריתם ששובר את ההצפנה – איך המבחין שנובע ממנו פועל? בערך – ממש בערך – הוא היה מצפין משהו בעזרת ה-x האקראי שהוא קיבל, נותן לאלגוריתם ש"שובר" את ההצפנה לעבוד, ואם האלגוריתם הצליח ובאמת שבר את ההצפנה, פולט 1. אחרת, הוא פולט 0. אם באמת השימוש במחולל פסאודו אקראי "החליש" את ההצפנה, אנחנו מצפים שבאופן משמעותי, המבחין שלנו יפלוט 1 בהסתברות גבוהה יותר אם מזינים לו ערכים מהמחולל הפסאודו אקראי, מאשר אם מזינים לו ערכים אקראיים "אמיתיים".

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

אז איך מגרילים מספרים במחשב?

שפת התכנות הראשונה שהכרתי הייתה BASIC, ואחד מהמשחקים הראשונים שכתבתי היה משחק שבו המחשב מגריל מספר והשחקן צריך לנחש אותו. למרבה הצער, שמתי לב לתופעה מאוד מוזרה במשחק שלי – המחשב תמיד ניחש את אותו המספר. לעומת זאת, אם הגדלתי את טווח המספרים שהוא יכל לבחור, הוא פתאום ניחש תמיד מספר אחר. רק אחרי זמן מה למדתי שכדי להבטיח שהמחשב יגריל מספר שונה בכל משחק, עלי לכתוב בתחילת התוכנית RANDOMIZE TIMER. מה זה בכלל אומר? מה הולך כאן?

שורש הבעיה נעוץ בכך שמחשבים הם יצורים דטרמיניסטיים באופיים – בהינתן המצב הנוכחי של תאי הזיכרון שלהם, הקידוד של החומרה שלהם וכו', המצב הבא נקבע באופן יחיד. אין "הגרלות" שמבוצעות בתוך המחשב – לפחות, כך אנחנו מקווים; התנהגות בלתי צפויה של המחשב (של המחשב עצמו, לא של תוכנה שמריצים עליו) מעידה על תקלה חמורה בו. אם כן, איך אפשר ליצור אקראיות מהדטרמיניזם הזה? האם בכלל יש משמעות למילה הזו, "אקראיות"?

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

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

הפתרון השני הוא להסתמך על גורמים שאינם בדיוק אקראיים מבחינה פיזיקלית, אבל לכל דבר ועניין נראים אקראיים – למשל, הקלדות על המקלדת. מן הסתם יש חוקיות גדולה למדי בהקלדות על המקלדת (למשל, כשכותבים באנגלית, צפוי שמייד אחרי כתיבת q ייכתב u), אבל אם נמדוד את קצב ההקלדה, ובפרט את מספר השניות שעוברות בין כל הקלדה לזו שאחריה, נראה משהו אקראי למדי (שכמובן, נע בתוך תחום מוגדר). באותו אופן גם תנועות העכבר הן "אקראיות" (חשבו על כך שכל תזוזה, ולו הקטנה ביותר של העכבר, נמדדת; כך שגם אם מזיזים את העכבר מנקודה א' לנקודה ב', יש אלפי דרכים שונות לעשות זאת, וזו שנבחרת היא "אקראית"). ישנם עוד גורמים "אקראיים" בריצת המחשב, אבל לא נתעמק ברובם; במקום זה נעסוק במקור הפופולרי והנפוץ ביותר לאקראיות, למרות שהחוקיות שבו גדולה מאוד – השעון הפנימי.

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

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

כל זה טוב ויפה, אבל נניח שאני רוצה מספר אקראי בין 1 ל-6, כלומר הטלת קוביה. מה אני עושה? אני לוקח את הפלט של הפעלה אחת של המחולל, ובמקום להשתמש במספר כמות שהוא (כי כאמור, זה יהיה מספר ענקי) אני בודק מה השארית שהמספר הזה נותן בחלוקה ב-6 (כלומר, מהו ערכו של המספר מודולו 6). זה יהיה ערך בין 0 ל-5, שמתפלג באופן אחיד אם המחולל אכן מוצלח (כלומר, אם אחזור על ההגרלה הזו פעמים רבות, אראה שכל מספר התקבל בערך שישית מהפעמים). בצורה דומה אפשר לקבל מספר טבעי בכל טווח שהוא; אפשר גם להגריל מספרים "ממשיים" בתחום שבין 0 ו-1, פשוט על ידי נרמול – חלוקת המספר האקראי שהמחולל החזיר, במספר המקסימלי שהוא יכול להחזיר. התוצאה שתתקבל תמיד תהיה מספר רציונלי, אבל ממילא רק מספרים כאלו מיוצגים במחשב בשיטת הייצוג הסטנדרטית.

כאן המקום לתאר כמה מחוללים קונקרטיים, אבל לא אעשה זאת, פשוט כי המתמטיקה של מחוללים טובים היא סבוכה וטכנית. אחד המחוללים הראשונים (אם לא הראשון) והפשוטים ביותר הומצא על ידי ג'ון פון נוימן – קח מספר בן n ספרות, העלה אותו בריבוע וקבל מספר עם 2n ספרות (הוסף 0 בתחילת המספר שהתקבל אם יש צורך), וכעת קח את n הספרות האמצעיות בתור הפלט. הבעיה בשיטה החביבה הזו היא שלולאות צצות יחסית מהר. פון נוימן, שמיוחס לו הציטוט "Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin."', כנראה לא נזקק למשהו מחוכם יותר, אבל בשימושים קריפטוגרפיים, ואפילו עבור משחקים, זה לא מספיק טוב. הנה עוד דוגמה למחולל שכנראה לא כדאי להשתמש בו, באדיבות xkcd:


כעת אפשר להסביר מהו ה-Randomize timer של בייסיק. כשקוראים בשפת תכנות כלשהי לפונקציה שמחזירה מספר אקראי, לרוב הפונקציה הזו משתמשת במימוש של מחולל פסאודו אקראי זה או אחר. הגרעין של המחולל הזה מאותחל כאשר התוכנית מתחילה לרוץ – השאלה היא לאיזה ערך הוא יאותחל. אם השפה מאתחלת אותו תמיד ל-0, מן הסתם תמיד נקבל את אותה סדרה אקראית. לכן כדי לקבל סדרות שונות בכל הרצה, יש להשתמש במקור שמובטח שתמיד יתן ערך שונה – והשעון הפנימי של המחשב הוא המקור המושלם לכך. הפקודה Randomize timer מאתחלת את הגרעין של המחולל האקראי לערך שמושג מהשעון; בשפות אחרות יש פונקציה בשם srand שעושה את אותו הדבר. אלא שאין הכרח לאתחל דווקא באמצעות השעות; אפשר לאתחל גם באמצעות ערך מספרי שמועבר על ידי המשתמש עצמו, וכאן נכנס לתמונה אחת ההיבטים החביבים ביותר של מחולל פסאודו אקראי – אפשר לשחזר במדוייק את הסדרות האקראיות שהוא מייצר. פשוט מכניסים שוב את אותו גרעין, ומקבלים את אותה סדרה. בשביל מה זה טוב?

לפני שנים הייתי מכור למשחק מחשב אווילי בשם Worms, שבו אתה שולט על צוות תולעים אכזריות שמפצפצות לצוותים אחרים של תולעים את הצורה עם בזוקות, רימונים ושאר מרעין בישין. הקרבות התנהלו בשדה קרב שנוצר באופן אקראי. חלק משדות הקרב היו מוצלחים במיוחד (נניח, הם היו עמוסים בצוקים שאם תולעת עומדת עליהם, אפשר לפצפץ אותה לאינסוף ביתר קלות). איך אפשר, אם כן, לשחזר את אותו שדה קרב? פשוט מאוד – לכל שדה קרב שהוגרל היה כתוב באופן מפורש הגרעין של המחולל הפסאודו אקראי שיצר אותו. כדי לקבל את אותו שדה קרב, היה צורך לכתוב מחדש את הגרעין, וזהו. כמובן, הגרעין נראה סתם כמו ג'יבריש מאוד ארוך וזה לא היה הכי פרקטי לזכור אותו; אבל אפשר היה להכניס גרעינים משלך, פשוט בתור טקסט באנגלית (ההמרה של הטקסט למספר בוצעה, כמובן, מאחורי הקלעים), ולראות מה קיבלת. כך למשל, אם היית כותב "Alamo" היית מקבל דבר אחד (שנוצר בצורה אקראית לגמרי, ויכל להיות מוצלח או לא מוצלח, תלוי במזל), ואם היית כותב "Waterloo" היית מקבל משהו שונה; אבל בכל פעם שבה היית כותב אותם, היית מקבל את אותו שדה קרב כמו בפעם הקודמת. התעלול נפוץ גם במשחקי מחשב אחרים, כמובן.

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

דוגמה שלישית, דומה ברוחה לדוגמה השניה – כשכותבים תוכנית מחשב, חלק בלתי נפרד מהתהליך הוא מה שנקרא Unit testing – הרצת בדיקות "נקודתיות" שבודקות פונקציות ומודולים ספציפיים בתוכנית. לעתים הבדיקות הללו צריכות להיות אקראיות, שכן המתכנת לא מצליח לחשוב בעצמו על כל המקרים המעניינים. במקרה שבו בדיקה אקראית גורמת לתקלה, חשוב שהפלט של המבחן, פרט ל"זה וזה נכשל" יהיה גם הגרעין שיצר את האקראיות שהובילה לתקלה – הרי ייתכן שבהרצות אקראיות אחרות לא תהיה שום תקלה, ואז איך נצליח לשחזר את התנאים שגרמו לתקלה כדי שנוכל לתקן אותם? (תיקון תקלות דורש לעתים קרובות הרצה חוזרת ונשנית של התוכנית על הקלט הבעייתי, עד שמתברר מה בדיוק הייתה הבעיה).

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

עוד על שימושי הקריפטוגרפיה בחיי היום יום (מציאת אוצרות ומעבר דרך קירות)

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

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

מה עכשיו?

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

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

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

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

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

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

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

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

(מזל טוב לנגה צבי, שזכתה במקום השני בתחרות חצי הגמר של Famelab בטכניון, עם סיפור שיתוף הסוד שהצגתי לעיל!)