Public Key คืออะไร?

public-key-cover-photo
01-public-key

Public Key เป็นเหมือนบัญชีธนาคารที่ทำให้คุณสามารถได้รับ Bitcoin ได้ 

Public Key จะสร้างมาจาก Private Key ของคุณ ซึ่งเหมือนกับรหัสผ่าน (Password) สำหรับตัวเลขบัญชี

เราจะสร้าง Public Key ได้อย่างไร?

คุณจะต้องใช้ Private Key (ซึ่งเป็นแค่ตัวเลขสุ่มขนาดใหญ่) เพื่อที่จะสร้าง Public Key ที่เกี่ยวข้องกัน 

คุณจะต้องทำ elliptic curve multiplication(การแก้โจทย์เส้นโค้งพีชคณิต)โดยใช้ Private Key ของคุณ ซึ่งจะมอบจุดที่ถูกต้องบน elliptic curve เป็นระยะพิกัด x และ y ซึ่งก็คือ Public Key ของคุณ

02-public-key-point

Code

นี้คือ Code สำหรับสร้าง Public Key จาก Private Key 

ผมจะไม่ได้อธิบายว่า elliptic curve ทำงานได้อย่างไร แต่มันจะรวมอยู่ในรหัสนี้อยู่ดีเพื่อที่จะแสดงให้คุณเห็นวิธีที่จะเริ่มคำนวณ Public Key สำหรับตัวเอง

Ruby

# example private key
private_key = "ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2"

# --------------------------
# Secp256k1 Curve Parameters
# --------------------------
# y^2 = x^3 + ax + b
$a = 0
$b = 7 # using global variables for convenience

# prime modulus
$p = 2 ** 256 - 2 ** 32 - 2 ** 9 - 2 ** 8 - 2 ** 7 - 2 ** 6 - 2 ** 4 - 1

# number of points on the curve
$n = 115792089237316195423570985008687907852837564279074904382605163141518161494337

# generator point (the starting point on the curve used for all calculations)
$g = {
  x: 55066263022277343669578718895168534326250603453777594175500187360389116729240,
  y: 32670510020758816978083085130507043184471273380659243275938904335757337482424,
}

# --------------------------
# Elliptic Curve Mathematics
# --------------------------
# Modular Inverse - Ruby doesn't have a built-in function for finding modular inverses, so here's one using the extended Euclidean algorithm.
def modinv(a, m = $p)
  a = a % m if a < 0 # make sure a is positive
  prevy, y = 0, 1
  while a > 1
    q = m / a
    y, prevy = prevy - q * y, y
    a, m = m % a, a
  end
  return y
end

# Double - Add a point on the curve to itself.
def double(point)
  # slope = (3x^2 + a) / 2y
  slope = ((3 * point[😡] ** 2) * modinv((2 * point[:y]))) % $p # using modular inverse to perform "division"

  # new x = slope^2 - 2x
  x = (slope ** 2 - (2 * point[😡])) % $p

  # new y = slope * (x - new x) * y
  y = (slope * (point[😡] - x) - point[:y]) % $p

  # return x, y coordinates of point
  return { x: x, y: y }
end

# Add - Add two points together.
def add(point1, point2)
  # double if both points are the same
  return double(point1) if point1 == point2

  # slope = (y1 - y2) / (x1 - x2)
  slope = ((point1[:y] - point2[:y]) * modinv(point1[😡] - point2[😡])) % $p

  # new x = slope^2 - x1 - x2
  x = (slope ** 2 - point1[😡] - point2[😡]) % $p

  # new y = slope * (x1 - new x) - y1
  y = ((slope * (point1[😡] - x)) - point1[:y]) % $p

  # return x, y coordinates of point
  return { x: x, y: y }
end

# Multiply - Use the double and add operations to quickly multiply a point by an integer (e.g. a private key).
def multiply(k, point = $g) # multiply the generator point by default
  # create a copy the initial starting point (for use in addition later on)
  current = point

  # convert integer to binary representation (for use in the double and add algorithm)
  binary = k.to_s(2)

  # double and add algorithm for fast multiplication
  binary.split("").drop(1).each do |char| # ignore first binary character
    # 0 = double
    current = double(current)

    # 1 = double and add
    if char == "1"
      current = add(current, point)
    end
  end

  # return the final point
  return current
end

# -------------------------
# Private Key To Public Key
# -------------------------
# convert private key to an integer
k = private_key.to_i(16)

# multiply generator point by this private key
point = multiply(k, $g) # this point is the public key

# convert x and y values of this point to hexadecimal
x = point[😡].to_s(16).rjust(64, "0")
y = point[:y].to_s(16).rjust(64, "0")

# uncompressed public key format (not used much these days, just showing how it looks)
public_key_uncompressed = "04" + x + y

# compressed public key format (every x value has a y that could be one of two possible points)
if (point[:y] % 2 == 0)
  prefix = "02" # if y is even
else
  prefix = "03" # if y is odd
end

public_key_compressed = prefix + x # only uses the full x coordinate

# -------
# Results
# -------
puts private_key           #=> ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2
puts public_key_compressed #=> 02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737

ทำไมถึงต้องใช้ Elliptical Curve

การใช้ elliptical curve multiplication(การคูณเส้นโค้งพีชคณิต) จะมอบข้อมูลการเชื่อมต่อทางคณิตศาสตร์ เปลี่ยนจาก Private Key ของคุณเป็น Public Key และยังมีคุณสมบัติสำคัญอีกสองประการ

1. มันไม่อาจทำงานย้อนกลับเพื่อที่จะดึงข้อมูล Private Key ได้

คุณสามารถใช้งาน elliptic curve multiplication ได้แต่ไม่อาจคำนวณย้อนกลับ

03-public-key-trapdoor

หมายความว่ามีการเชื่อมต่อทางคณิตศาสตร์ระหว่าง Private Key และ Public Key แต่ไม่มีใครสามารถใช้ Public Key ในการค้นหา Private Key ของคุณได้

จากนั้น คุณสามารถมอบ Public Key ให้กับคนอื่นๆ ได้ และเก็บ Private Key ไว้เป็นความลับ

2. คุณสามารถพิสูจน์ได้ว่าคุณมี Private Key โดยที่ไม่จำเป็นต้องมอบให้คนอื่น

โดยพื้นฐานแล้ว โดยการใช้การคูณเส้นโค้งพีชคณิต(elliptic curve mathematics) มากขึ้นอีก คุณจำเป็นต้องสร้างลายเซ็นดิจิทัลเพื่อที่จะพิสูจน์ว่า Private Key ของคุณเกี่ยวเนื่องกับ Pucblic Key โดยที่ไม่จำเป็นต้องมอบ Private Key ของคุณให้กับคนอื่น 

เหมือนกับการที่คุณบอกว่าคุณมีรหัสผ่านสำหรับบัญชีผู้ใช้งาน แต่คุณไม่จำเป็นต้องเปิดเผยรหัสผ่านของคุณเพื่อพิสูจน์ว่าคุณมีมันจริงๆ

04-digital-signature

ต้องขอบคุณเวทมนต์ของลายเซ็นต์ดิจิทัลและมันเกิดจากเส้นพีขคณิต(elliptic curve mathematics)

รูปแบบของ Public Key

Public Key เป็นเพียงแค่ ระยะพิกัด x และ y บน elliptic curve โดยปกติแล้วมันจะเก็บรูปแบบของเลขฐานสิบหกเอาไว้ 

นี้คือ 2 รูปแบบของ Public Key

1. ไม่ถูกบีบอัดข้อมูล (Uncompressed)

นี้เป็นรูปแบบเก่า ซึ่งส่วนมากแล้วจะไม่ถูกนำมาใช้งานเพราะผู้คนนิยมแบบบีบอัดข้อมูลที่สั้นกว่า

แรกเริ่มเดิมที Bitcoin จะใช้ทั้งระยะพิกัด x และ y อยู่ในพิกัดเดียวกันเพื่อที่จะเก็บ Public Key 

ในรูปแบบไม่บีบอัดข้อมูลนี้ เราแค่วางระยะพิกัด x และ y ที่สอดคล้องกัน จากนั้นก็วาง 04 ไว้ข้างหน้าเพื่อที่จะแสดงให้เห็นว่ามันเป็น Public Key แบบไม่ถูกบีบอัดข้อมูล

05-public-key-uncompressed

นี้คือตัวอย่างรูปแบบของ Public Key แบบไม่ถูกบีบอัดข้อมูล  

public key (uncompressed) = 04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4

2. แบบบีบอัดข้อมูล (Compressed)

อย่างไรก็ตาม เพราะว่า elliptic curve คือความสมมาตรตามแกน x แต่ละระยะพิกัด x จะมีความเป็นไปได้เพียงแค่ 1 ใน 2 ระยะพิกัด y เท่านั้น 

06-elliptic-curve-symmetry

และนี้คือเคล็ดลับ…. 

  • หาก Y มีค่าเป็นคู่ มันจะสอดคล้องกับจุดใดจุดหนึ่งบนเส้น  
  • หาก Y  มีค่าเป็นคี่ มันจะสอดคล้องกับจุดอื่น 

เพราะฉะนั้นในรูปแบบของ Public Key ที่ถูกบีบอัดข้อมูล เราแค่เก็บค่าเต็มของ x ที่สอดคล้องกัน ไว้กับค่านำหน้าที่ชี้ว่า y มีค่าเป็น คู่ หรือเป็น คี่ 

07-public-key-compressed

เราแค่ต้องเก็บเอาไว้ไม่ว่า ค่าของ Y ที่สอดคล้องกันจะเป็นคู่หรือคี่

นี้เป็นตัวอย่างของ Public Key ที่ ถูกบีบอัดข้อมูล เทียบกับที่ ไม่ถูกบีบอัดข้อมูล

public key (uncompressed) = 04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4
public key (compressed)   = 02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737

นี้เป็นรูปแบบข้อมูลที่ถูกบีบอัดที่จะช่วยเราในการทำงานกับค่าเต็มของ X และ Y ที่สอดคล้องกันและช่วยประหยัดพื้นที่ข้างใน Blockchain (ยกตัวอย่างเช่น เมื่อเราสร้างรายการธุรกรรมที่ล็อกข้อมูล Output ไว้กับ Public Key เฉพาะ)

เราจะสามารถขยายข้อมูล Public Key ที่ถูกบีบอัดไว้ได้อย่างไร?

คุณสามารถขยายข้อมูล Public Key ที่ถูกบีบอัดไว้โดยการแก้โจทย์สมการกราฟ y^2 = x^3 + 7.

นี้จะช่วยให้คุณหาค่า Y ที่เป็นไปได้สำหรับกุญแจที่ไม่ถูกบีบอัดข้อมูล คุณสามารถใช้ค่านำหน้าจากกุญแจที่ถูกบีบอัดข้อมูลไว้เพื่อที่จะกำหนดว่าค่า Y ใดที่จะต้องนำมาใช้ (เพราะว่าค่าสแควรูทของเลขใดๆ ก็ตามจะมีคำตอบที่เป็นไปได้สองคำตอบ ยกตัวอย่างเช่น 16 = +4 or –4)

Ruby

# Compressed public key
compressed = "02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737"

# Split compressed key in to prefix and x-coordinate
prefix = compressed[0..1]
x      = compressed[2..-1].to_i(16)

# Secp256k1 curve parameters
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f

# Work out y values using the curve equation y^2 = x^3 + 7
y_sq = (x**3 + 7) % p # everything is modulo p

# Secp256k1 is chosen in a special way so that the square root of y is y^((p+1)/4)
y = y_sq.pow((p+1)/4, p) # use modular exponentation

# Use prefix to select the correct value for y
# * 02 prefix = y is even
# * 03 prefix = y is odd 
if (prefix == "02" && y % 2 != 0) # if prefix is 02 and y isn't even, use other y value
    y = (p - y) % p
end
if (prefix == "03" && y % 2 == 0) # if prefix is 03 and y is even, use other y value
    y = (p - y) % p
end

# Construct the uncompressed public key
x = x.to_s(16).rjust(64, "0") # convert to hex and make sure it's 32 bytes (64 characters)
y = y.to_s(16).rjust(64, "0")
uncompressed = "04" + x + y

# Result
puts uncompressed #=> 04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4

Public Key ใช้ใน Bitcoin อย่างไร?

คุณสามารถมอบ Public Key ให้กับคนอื่นเพื่อที่พวกเขาจะได้รวบรวมมันเข้าไปในสคริปต์สำหรับล็อกของข้อมูล Output เมื่อพวกเขาสร้างรายการธุรกรรม

08-transaction-pubkey

เราสามารถมอบ Public Key ให้กับคนที่ต้องการส่ง Bitcoin ให้กับเรา สิ่งนี้เรียกว่า Pay-to-Pubkey (P2PK)

อย่างไรก็ตาม ใน Bitcoin ตอนนี้เราใช้ hash160 กับ Public Key ก่อนที่จะมอบให้คนอื่น
จากนั้น Public Key จะถูกใช้ก็ต่อเมื่อเราจำเป็นต้องปลดล็อกข้อมูล Output (ล็อกเดิมจะตรวจสอบก่อนว่า Public Key มีการ Hash อย่างถูกต้องก่อนที่จะทำการตรวจสอบเทียบกับลายเซ็นอีกครั้ง)

09-transaction-pubkey-hash

Hash160 ของ Public Key ตอนนี้อยู่ในล็อก เราจะเรียกสิ่งนี้ว่า Pay-To-Pubkey-Hash (P2PKH)

เราจะหา Public Key ได้จากที่ไหนใน Blockchain?

เราจะหา Public Key ได้จากที่ไหนใน Blockchain 

หากคุณกำลังค้นหาผ่านข้อมูล Blockchain ดิบ คุณจะเจอ Public Key ได้ในข้อมูลรายการธุรกรรม 

ในรายการธุรกรรม P2PKH แบบพื้นฐาน ยกตัวอย่างเช่น 

  1. ตัวเลข Hash ของ Public Key จะอยู่ในโค้ดสำหรับล็อกข้อมูล (ScriptPubKey) ของข้อมูล Output
01000000017dc9d3eaa91ef9886e48929285243d945a20be621a7483d5442872c2f4bbf432000000004a493046022100ea
5812a1cbcf9c8c49fdfb4ed7ef89c05d9d11a5df941fd76546e9031fefbef5022100f5458675ebd56db5517510527afa5f
f7c98e08d7ed83a8b180f7ac841531a3ac01
ffffffff
0100f2052a010000001976a914fc50c5907d86fed474ba5ce8b12a
66e0a4c139d8
88ac
00000000

จากนั้นก็ในรายการธุรกรรมต่อไปที่ใช้ Bitcoin

2. สามารถค้นพบ Public Key ดั้งเดิมได้ในโค้ดปลดล็อก (ScriptSig) ในข้อมูล Input

0100000000100000001f7d7667421677ae9bce69e558048e0aca48d704c1dc446cdec80c5e77df7c124000000008b48304
5022100b92b0d78a1a72b25179260e96a15efe95f98962622fb232f92d6c6ef20e15e9b022061c946c3f976339e370eabd
256d91aa4711bb9985330f7d18ee77987b0ca24300141046c04c02f1138f440e8c5e9099db938bfba93d0389528bb7f6bf
423ae203a2edcfba133f0409023d7ea13ac01c5aeedaf0bbfbeb8b82e9b48410d93a296da5b0c
ffffffff
0100f2052a010
00000
1976a914e6a874331cddf113e6f424f547aa93c10755d5e688ac
00000000