הוכחת עבודה

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

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

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

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

ניקח כדוגמא את פונקציית ה Hash שנקראת sha1. להלן מספר ערכי Hash למילים נפוצות:

sha1(hello) = aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
sha1(nice to meet you) = a2a75a27ba7f3f08d2f237b8ca41343a97e27a5e
sha1(abc123) = 6367c48dd193d56ea7b0baad25b19455e529f5ee

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

http://www.sha1-online.com

מצד שני חישוב ערך sha1 של מיליוני מילים כן ייקח זמן ולכן המשימה למצוא מחרוזת שמתחילה במילה מסוימת וערך ה sha1 שלה מסתיים ב X אפסים עשויה להיות מאוד קשה.

ניקח לדוגמא את התחילית ynon. המילים הבאות מתחילות ב ynon ולידן ערכי ה sha1 שלהן שמסתיימים באפסים:

sha1(ynon4)        = a51b25e576433aa9e344fdf660055cbb46be75a0
sha1(ynon280)      = 8efd31f0e08b07edc33feb2f163f600a591b7e00
sha1(ynon9203)     = c2308b862533f09d634f159d3cb2f9788a456000
sha1(ynon27700)    = c1826410aa2800333463b5def882d3218dc00000
sha1(ynon68749224) = 27ebde86cb646b8e24648f97024a37e9fb000000

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

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

אגב לסקרנים זו התוכנית בשפת רובי איתה מצאתי את ערכי ה SHA1 הרלוונטים:

require 'digest'

class Worker
  attr_reader :prefix, :target_zeros_count, :hasher

  def initialize(prefix, hasher=Digest::SHA256)
    @prefix = prefix
    @hasher = hasher
  end

  def search(target_zeros_count)
    @target_zeros_count = target_zeros_count

    counter = 0
    loop do
      break if ready?(counter)
      counter += 1
    end

    plaintext_with_counter(counter)
  end

  def plaintext_with_counter(counter)
    "#{prefix}#{counter}"
  end

  def hash_with_counter(counter)
    hasher.hexdigest plaintext_with_counter(counter)
  end

  def ready?(counter)
    hash_with_counter(counter).end_with?('0' * target_zeros_count)
  end
end

w = Worker.new(ARGV[0], Digest::SHA1)
res = w.search(ARGV[1].to_i)
puts res
puts Digest::SHA1.hexdigest(res)