BIP32 บอกอะไรเรา
BIP32 เป็น Standard ที่อธิบายเรื่องโครงสร้างตามลำดับชั้นของกระเป๋า เพื่อให้เข้าใจได้ง่าย อยากให้ดูรูปนี้ก่อน
รูปนี้ คือรูปที่อธิบายแบบให้เข้าใจได้ง่ายที่สุด ว่าจริงๆแล้ว Seed Phrase (seed word / recovery word หรือ คำอื่นๆที่สื่อสิ่งเดียวกัน) จะถูกใช้เพื่อสร้าง Master Seed ฝั่งซ้าย จากนั้น ก็เอามาสร้าง master node (private key + public key คู่แรกสุดที่ได้จาก Master Seed เดี๋ยวอธิบายด้านล่าง) หนึ่งอันก่อน จากนั้น จึงเอาไปใช้สร้าง Wallet ในชั้นต่างๆ ทั้งตามลำดับชั้น (depth) และ ลำดับในชั้นเดียวกัน (i) ทางขวาไปเรื่อยๆ อารมณ์แบบ ออกลูกออกหลานได้ แบบนั้นเลย
และแน่นอนครับ จาก diagram ตัวนี้เรามี seed word เพียงชุดเดียว สามารถสร้าง กระเป๋าได้เป็นจำนวนแทบจะอนันต์เลยทีเดียว ถ้าคนที่เคยสร้าง new wallet ใน metamask โดยไม่ได้เปลี่ยน seed word มันก็คือกระบวนการทำงานแบบนี้นี่แหล่ะครับ
ย้อนกลับมาที่ BIP32 อีกที จุดประสงค์ที่เค้าสร้าง standard นี้ขึ้นมา ก็เพื่อให้เราสามารถสร้างกระเป๋าเป็นจำนวนมากได้ โดยที่มีการแบ่งโครงสร้างลำดับชั้นออกจากกันได้ด้วย คือไม่ได้สร้างแบบเส้นตรงจาก 1 ไป 100 เลย แต่เราควรแบ่งกลุ่ม เช่น กลุ่ม A ใบที่ 1 ถึง 50 และกลุ่ม B ใบที่ 1 ถึง 50 เป็นต้น และเค้าคิดเผื่อไปถึงในกรณีที่สร้างกระเป๋าแบบใช้สำหรับการ monitoring ค่าขาเข้าอย่างเดียวด้วย เช่น กระเป๋า donate สำหรับการเข้าห้องน้ำสาธารณะ เป็นต้น เราก็มี public key ให้ donate ได้อย่างเดียว แต่เอา private key แยกไปเก็บที่อื่นเพื่อใช้สำหรับการสั่งโอนออกจากส่วนกลางอีกที อะไรแบบนั้น
และมาตรฐานตัวนี้แหล่ะ ที่เป็นตัวแม่เลย ทำให้ทุก wallet ที่ implement จากคนละผู้ให้บริการ สามารถทำงานร่วมกันได้ (หากระเป๋าเจอ เวลาเอา seed ไป import)
ถ้าดูจากรูป จะเห็นว่าที่น่าสนใจ ก็คือ block master seed , block master node , block wallet/accounts, block wallet chain ทั้งสี่ block นี้ เค้ากำหนดเป็นแนวทางให้เราใช้งานเลย กล่าวคือ (ตามที่ BIP44 เค้าแนะนำเอาไว้ และเป็นมาตรฐานที่คนส่วนใหญ่ใช้งานในตอนนี้อยู่ เลยยกมาเล่าคร่าวๆก่อน )
master seed นี่ก็คือ seed word ในรูป bytes ที่ได้มาจากการ hash seed word อีกที จะมีค่าเดียวเท่านั้น
master node นี่เรียกได้ว่าเป็นกระเป๋า ใบแรก ที่ตั้งต้นขึ้นมาจาก seed word แต่ว่าเราจะไม่ใช้มัน แต่เราจะหยิบเอา private key , public key จากตรงนี้ ไปใช้เป็นค่าเพื่อป้อนให้กับลำดับชั้นต่อไปอีกที เพื่อเพิ่มจำนวน และเพิ่มความปลอดภัยของกระเป๋าข้างใน และยังเป็นการเพิ่ม use case ให้สามารถเอาไปใช้งานในการรับเข้าอย่างเดียวได้ด้วย (แยก wallet address ออกจาก private key)
Wallet / Account นี่คือ ค่าที่บอกว่า เป็นกระเป๋าของ Account ประเภทไหน ซึ่งปัจจุบัน เราใช้ block นี้มานิยามว่า นี่เป็นกระเป๋าที่ทำงานตาม standard ใด ที่นิยมตอนนี้ก็คือ 44 ก็คือ (ทำงานตาม standard BIP44) ที่ใช้กันแพร่หลาย ทั้ง BTC, ETH และอื่นๆ 49,84,141 คือกระเป๋าทำงานแบบ segregated witness transactions หรือที่เรียกกันว่า SegWit แต่แตกต่างกันในการทำงานภายในอีกที ที่ก็ว่าไปตาม standard BIP49 , BIP84,BIP141 นั่นเอง
Wallet Chains นี่คือค่าที่บอกว่าเรากำลังใช้งาน Network (blockchain) ใดอยู่ เช่น 0 คือ BTC , 60 คือ ETH ซึ่งค่าตัวเลขตรงนี้ เค้าจะมีคำขยายอธิบายเพิ่มเติมใน SLIP44 อีกที
Addresses (Account) นี่แหล่ะ คือโครงสร้าง ที่เราสามารถกำหนดได้อย่างอิสระ เพื่อเอามาทำเป็นโครงสร้างกระเป๋าได้ โดยเค้าจะมีให้เราใช้งานได้ 3 ชั้นลึก อธิบายแต่ละชั้นได้ดังนี้
ชั้นแรกของ Account ก็คือตัว account เอง อันนี้เรากำหนดอิสระได้เลย ตั้งนิยามเอามาใช้ได้เลย ว่าเลขไหนคือ ใช้เพื่ออะไร เป็นเลข running ที่เริ่มต้นตั้งแต่ 0 เช่น 0 ใช้เพื่อเป็น saving wallet, 1 ใช้เพื่อเป็น donate wallet เป็นต้น โดยเค้าแนะนำว่า ไม่ควรสร้างมาทิ้งเอาไว้ ถ้ากระเป๋าใบก่อนหน้ายังไม่ถูกใช้งาน ดังนั้น ควรไล่ใช้ตั้งแต่ 0 1 2 …. ไปเรื่อยๆ และถ้าเราอ้างอิงจาก BIP32 จะมาสุดแค่ชั้นแรกนี้เท่านั้นนะครับจะไม่มีอีกสองชั้นถัดไปจากนี้ อีกสองชั้น ก็จะสร้างขึ้นมาด้วยวิธีการเดียวกันกับที่สร้างชั้นนี้ (มัน loop ไปอีกสองชั้น) แต่จะไปอธิบายความหมายด้วย BIP44 ครับ แต่ผมจะอธิบายต่อไปเลย เพราะปกติเราจะใช้ BIP44 กันเป็นส่วนใหญ่อยู่แล้ว
ชั้นสองของ Account ก็คือ External / Internal ถ้าเป็น 0 ใช้สำหรับ external chain , 1 สำหรับ internal chain เช่น เราใช้ 0 สำหรับ public ETH network แต่ว่าเราก็มีการรัน ETH chain ในบริษัทเราเอง (ซึ่งเค้าเรียกว่า private chain) แต่เราจะใช้ seed เดียวในกาารควบคุมกระเป๋าทั้งภายนอกและภายใน (ที่เป็นคนละ address กัน) ก็ใช้กำหนดให้เป็น 1 สำหรับ private network และ 0 สำหรับ public network โดยที่ลำดับที่ 3 ใช้ตรงกันได้อยู่เช่นเดิม หรือในอีก meaning ที่ BTC ใช้งานอยู่ ก็คือ ใช้สำหรับเก็บ ‘เงินทอน’ ในกระบวนการทำงานแบบ UTXO ของ bitcoin ครับ ก็จะใช้ 1 เป็นกระเป๋ารับเงินทอนนี่แหล่ะ ซึ่ง address นี้จะไม่ public หรือเอาไปใช้งานโดยทั่วไป แต่จะเอามาใช้กันเองเป็นการบริหารจัดการภายในเท่านั้น
ชั้นสามของ Account ก็คือ Index อันนี้ตรงไปตรงมาเลย ก็คือ เป็น running number ของกระเป๋าที่ผ่านการนิยามจากข้างบนมาจนถึงลำดับนี้ครับ กระเป๋าใบที่ 0 ใบที่ 1 ใบที่ 2 ไล่ไปเลย ง่ายๆแบบนี้ครับ
เหตุผลเบื้องหลังจาก BIP32
ถ้าไม่มีมาตรฐานนี้ ให้ลองจินตนาการถึง public key + private key ที่ต้อง random ทุกครั้ง ที่จะสร้างกระเป๋าใบใหม่ แม้ว่าจะใช้เพื่อทำงานในระบบเดียวกัน (เช่น บริษัทใหญ่ๆ บริษัทหนึ่ง จะสร้าง wallet สำหรับพนักงานทุกคน ซึ่งมันก็จะเป็น กึ่ง centralized ประมาณหนึ่ง เผื่อพนักงานลาออก ก็จะ inactive ได้เป็นต้น) ดังนั้น เราต้องเสีย resource ในการ random เพื่อสร้างให้ครบ เท่านั้นไม่พอ ต้องหาทางจัดเก็บ private key มากมายจากการ random เหล่านั้นอีก และยามที่จะต้องใช้ คราวนี้ล่ะวุ่นสุดๆเลย เก็บไม่ดีเข้าถึงได้ง่าย ความปลอดภัยก็ต่ำอีก จึงเป็นที่มาของ Deterministic wallets และเพื่อให้ได้ use case ที่กว้างขวางขึ้นไปอีก โดยยังทำให้ปลอดภัยอยู่ได้ จึงทำให้เป็น Hierarchical ด้วย (จึงกลายเป็น Hierarchical deterministic wallets) ที่อธิบายใน BIP32 นี่เอง
องค์ประกอบที่สำคัญของ BIP32
มีสองส่วนที่สำคัญ ส่วนแรก คือการสร้าง master node จาก master seed ซึ่งจะเป็นหนึ่งต่อหนึ่งเท่านั้น และมีการทำงานที่แตกต่างจากส่วนที่สอง ก็คือ การสร้างกระเป๋าขึ้นมาตามโครงสร้างลำดับชั้นแบบกิ่ง ที่เริ่มต้นจากค่าเดียว สร้างได้มากมายอย่างที่ว่าไปแล้วนั่นล่ะ
อธิบายการทำงานส่วนการสร้าง Master key
การสร้าง Master key นี่คือ จุดเริ่มต้น ที่จะต้องใช้ Seed word ที่หลายคนคุ้นเคยกันดีนั่นแหล่ะ (ซึ่งมีคำอธิบายเรื่อง seed word เอาไว้อีกที่ BIP39 แต่จะยังไม่พูดถึงตอนนี้) โดยขั้นตอนก็คือ
- เตรียม seed word ต้องใช้ระหว่าง 128 bits ถึง 512 bits (128 bits = 12 คำ, 256 bits = 24 คำ ซึ่ง Metamask โดย default ตอนนี้ยังใช้ 128 bits อยู่ ดังนั้น มีค่าความเป็นไปได้ที่จะถูก crack ที่มากกว่า 24 คำหรือ 256 bits นะครับ แต่ 128 bits เนี่ย ก็ใช้เวลาเป็นศตวรรษแล้วในการ crack ได้)
- หาค่า I ด้วยการเข้ารหัส HMAC-SHA512(“Seedword ในรุป binary”, “Seedword ในรูป byte”)
- เอา I มาแบ่งสองส่วน ส่วนละ 32 Byte จะได้ IL และ IR
- ใช้ parse256(IL) จะทำให้ได้ master private key และ IR คือ master chain code
เราก็จะได้ master key ออกมาแล้วครับ คือ master private key , master chain code นั่นเอง
สร้าง Root key (master node)
จากนั้นเราจะเริ่มสร้าง Root key (master node) กันต่อ ซึ่งถือได้ว่าเป็นกระเป๋าใบแรกสุดที่ได้จาก seed นั้นเลยก็ว่าได้ จะเอาไปใช้เลยก็ได้นะครับ แต่มันจะไม่ได้เป็น Hierarchical deterministic wallets เท่านั้นเอง (และจริงๆก็ไม่มีใครใช้กันด้วยแหล่ะ)
เริ่มต้นมที่สร้าง extended private key (xprv) จาก master private key โดยใช้ base58 encoding ครับ เรื่องนี้น่าสนใจตรง base58 encode นี่แหล่ะ
Base58 encode มันคือ ตัวหนังสือภาษาอังกฤษและตัวเลข ที่ตัด 0 O I l (ศูนย์ โอใหญ่ ไอใหญ่ แอลเล็ก) ออกไป ทำให้เหลือเพียง 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz เท่านั้น ที่ต้องตัด 4 ตัวนั้นออก เพราะมันดูเหมือนกันครับ ทำให้แยกออกจากกันยาก แต่ว่า 1 ยังอยู่นะ เพราะว่าไม่ซ้ำกับ ไอใหญ่ แอลเล็ก แล้ว
หลังจากที่ ผ่าน base58 encode มาแล้ว ก็จะได้หน้าตาประมาณนี้ xprv9vfrdimrsgZqqCgsVhAoVeUpZ8uXfkqkGD7SCphDgFKYoWAgBm2uybF7NSVgDC5oB4MNNc9JtZ3tkMEyoUDTPaFTgjAqWaF6qEwiAbktuLJ
โดย xprv ถูกเติมเข้ามา เพื่อใช้บอกว่านี่คือ extend private key นะจ๊ะ
การทดสอบว่า encode ได้ถูกต้องแล้ว
ตามมาตรฐานจะมีการทดสอบโดยใช้วิธีการ test vector ซึ่งจะไม่กล่าวแล้วกันจะลึกเกินไป เพื่อใช้ทดสอบว่าสิ่งที่เราตั้งต้นจนมาถึงสิ่งที่ encode ล่าสุดนั้นผลลัพธ์ออกมาถูกต้องหรือไม่
อธิบายการทำงานส่วนการสร้าง Extended key
เมื่อเราได้ master private key แล้ว เราจะสามารถใช้ master private key นี้ ในการไปสร้าง Extended private key ต่อ เราจะยังไม่พูดถึงการสร้าง master public key เพราะมันง่ายมาก เดี๋ยวจะไปเล่าตอนท้ายอีกที
ถ้าเราไม่เอา master private key ไปสร้าง extended key ต่อ เราจะได้กระเป๋าใบเดียวเท่านั้นสำหรับ seed แต่ละชุด ซึ่งไม่มีใครทำกันครับ
การที่เราสร้าง Extended key จะทำให้เราได้ กระเป๋าตามลำดับชั้นออกมา เพื่อไปใช้งาน use case ต่างๆได้โดยใช้ seed ชุดเดียวเท่านั้น
Derivation path เป็นอีกคำศัพท์ที่จำเป็นต้องรู้จัก เพราะมันคือตัวที่ใช้บ่งบอก สองส่วน คือ ลำดับชั้น และ ลำดับของแต่ละชั้น ให้นึกภาพตามว่า ตึกนี้สูง 5 ชั้น แต่ละชั้นมี 2 พันล้านห้อง แต่ละห้อง ก็คือ 1 wallet ดังนั้น เวลาที่เราจะไขกุญแจของห้องไหน เราจะต้องรู้ว่า อยู่ชั้นไหน และ ห้องลำดับที่เท่าไร นั่นเองครับ นี่คือ Derivation path ซึ่งหน้าตา ก็จะเป็นแบบนี้ m/44’/60’/0’/0/0 สำหรับ ETH และเป็น m/44’/0’/0’/0/0 แบบนี้ สำหรับ BTC ความแตกต่าง จะไปอธิบายเอาไว้ใน BIP44 อีกทีครับ (จริงๆมันคือ master / wallet / chain / account / external,internal / index ตามที่อธิบายไว้ย่อหน้าบนๆนั่นล่ะ)
กลับมาที่การสร้าง child key อีกครั้ง เรามี master key ในมือแล้ว แล้วเราก็กำหนดได้แล้วว่าเราจะใช้ Derivation path ที่เบอร์ไหน เราจะมาสวมร่างเป็นช่างทำกุญแจ เพื่อสร้างลูกกุญแจสำหรับไขห้องนั้นขึ้นมาครับ
โดยสิ่งที่ต้องใช้ในขั้นตอนนี้จะประกอบด้วย
- Private key ได้จากข้างบนแล้ว
- Chain code ได้จากข้างบนแล้ว
- Child number อันนี้แหล่ะ ที่ต้องระบุ ว่าเป็น ‘ห้องไหน’ แต่ว่า ไม่ได้ระบุเข้าไปทั้งก้อน Derivation path นะครับ
อธิบาย child number เพิ่มอีกนิด ก็คือ มันระบุแค่ห้องไหนเท่านั้น นั่นหมายความว่า ถ้าเราจะทำชั้นที่ 3 (สมมุติ) เราจะต้องวน loop ตั้งแต่ชั้นที่ 1 ไปจนถึงชั้นที่ 3 ครับ ซ้อนกันเป็นชั้นๆ โดยข้อมูลของชั้นบน จะได้มาจากการเข้ารหัสข้อมูลของชั้นล่างอีกที (ซับซ้อนยิ่งนัก)
โดยขั้นตอนนี้ สิ่งที่เค้าจะทำ มีดังนี้ครับ
- หา child number (path number) : child number จะหาได้จาก เลข derive บวกด้วย 231 หากเป็น hardened key แต่ถ้าเป็น normal key ก็ใช้เลข derive ได้ตรงๆเลย เช่น m/44′ นี่คือ hardened key (มี ‘ อยู่ที่ด้านหลัง) ดังนั้น path number คือ 44 + 231 = 2147483692 แต่ถ้าเป็น m/44 ก็ใช้ 44 ได้เลย ดังนั้น m/44’/60’/0’/0/0 เราจะได้ child number แต่ละตำแหน่งเป็นค่าดังนี้ (2147483692, 2147483708, 2147483648, 0, 0)ตามลำดับ
- Finger print : finger print นี้ จะได้มาจากการ เอา parent private key ไปแปลงเป็น public key จากนั้น hash อีกทีแล้วตัดเอามา 32 bits แรก ก็คือ finger print (อันนี้พยายามอธิบายแบบให้เข้าใจง่ายๆแล้วนะ ของจริงมันมีรายละเอียดมากกว่านี้) ประโยชน์ของมันคือ เป็นวิธีใช้ตรวจสอบ parent ที่ง่ายและเร็ว เพราะถ้าป้อน parent private key ผิดแล้วเอาไปผ่านกระบวนการหา finger print ก็จะได้คนละค่ากันทันที
- ทำการสร้าง extended private key ตาม derive path ที่กำหนด : เอาล่ะ หลังจากที่เตรียมข้าวของและอุปกรณ์สำหรับทำลูกกุญแจพร้อมแล้ว และกำหนดเป้าหมายแล้ว ก็เริ่มทำลูกกุญแจ โดยการใช้ private key + chain code + child number เอาไปสร้าง private key ที่เป็นกระบวนการเดียวกันกับการสร้าง master private key ที่อธิบายไปข้างบนนั่นเลย แต่เปลี่ยนจาก HMAC-SHA512(Key = “Seedword in bytes”, Data = S) ให้เป็น HMAC-SHA512(chain code , Data) โดย Data ในที่นี้ ได้มาจาก แปลง private key ให้เป็น bytes สำหรับ hardened key หรือค่าที่ได้จากกระบวนการที่คล้ายกับการหา finger print สำหรับ child number ที่ไม่ใช่ hardened key
- ถึงตรงนี้ ผลลัพท์ที่ได้ ก็จะเป็น private key และ chain code นั่นเองครับ (เหมือนขั้นตอนการสร้าง root key ละ)
- จากนั้น ก็วน loop กระบวนการทั้งหมดไปจนครบ derive path คือ มันจะเริ่มทำ m/44′ ก่อน แล้วจึงเอาข้อมูลที่ได้ ไปทำ m/44’/60′ ต่อ ไปจนจบทุกชั้น m/44’/60’/0’/0/0 ก็จะได้ลูกกุญแจเอาไว้ไขห้อง m/44’/60’/0’/0/0 อย่างที่ต้องการ
จบแล้วครับ สำหรับการสร้างลูกกุญแจ
การสร้างลูกกุญแจห้องต่อไปทำอย่างไร
ทำแบบเดิม แต่เปลี่ยน Derivation path จากm/44’/60’/0’/0/0 เป็น m/44’/60’/0’/0/1 เท่านั้นครับ ซึ่งนี่แหล่ะ คือสิ่งที่ metamask และกระเป๋าอื่นๆตาม standard BIP32+BIP44 เค้าทำกัน ซึ่งอันนี้มีอธิบายอีกทีใน BIP44
สร้าง Public key
ตามที่ติดค้างเอาไว้ เพราะเราเอาแต่สร้างลูกกุญแจอย่างเดียวเลย ยังไม่ได้สร้างแม่กุญแจขึ้นมาเลย และอย่างทื่บอกว่า Public key เนี่ย ก็สร้างมาจาก private key นี่แหล่ะ มันไม่ซับซ้อนเท่ากับ private key เลยไม่อยากขัดขั้นตอนการสร้าง private key อันสลับซับซ้อนให้งงไปก่อน
Public key จะได้มาจาก การเอา Private key ไปผ่าน กระบวนการที่คล้ายกับการสร้าง finger print ก็คือ
p = curve_point_from_int(private_key)
public_key_bytes = serialize_curve_point(p)
แล้วแปลง public_key_bytes กลับมาเป็น hex อีกที เติม 0x ไปข้างหน้า ก็จะได้หน้าตาที่เราคุ้นเคยกัน เช่น 0x024c8f4044470bd42b81a8b233e2f954b63f4ee2c32c8d44288b44188754e2042e นั่นเอง
แต่….. โดยปกติแล้ว ไม่ควรเอา public key ไปใช้รับเงินกันนะครับ อันเนื่องมาจากเหตุผลด้านความปลอดภัย และความเหมาะสมในการใช้งานจริง เค้าจะเอา public key ไปสร้าง wallet address กัน
สร้าง Wallet address (public address)
เมื่อไม่เอา public key ไปใช้รับเงินกัน ก็เลยเอามาผ่านกระบวนการ hash และตัดสั้นอีกรอบ เพื่อสร้างให้เป็น Wallet address อันนี้ล่ะที่จะเอาไว้ใช้รับเงินละ (จริงๆส่วนนี้ อธิบายไว้ใน BIP44 เดี๋ยวเอามาเล่าอีกที ตอนนี้เอาให้พอเข้าใจภาพรวมๆก่อน) โดยการทำ
digest = keccak(x.to_bytes(32, ‘big’) + y.to_bytes(32, ‘big’))
address = ‘0x’ + digest[-20:].hex()
จะเหลือ 0xbbec2620cb01adae3f96e1fa39f997f06bfb7ca0 จะเห็นได้ว่า สั้นลงเยอะเลย อันนี้น่าจะคุ้นๆตากันที่สุดละ ก็ที่เราใช้กันทั่วไปนี่แหล่ะครับ
และทั้งหมดนี้ ก็คือการสร้าง wallet จาก seed word เพียงแค่ชุดเดียว ที่สามารถสร้างได้เยอะแยะมากมายแทบจะเรียกได้ว่าเป็นอนันต์เลยก็ว่าได้ และยังอธิบายด้วยว่า private key มันสร้างมาจากไหน และสร้าง public key ขึ้นมาได้อย่างไร รวมไปถึงการแบ่งโครงสร้างกระเป่าออกเป็นชั้นๆได้อย่างไร
ถ้าอ่านถึงตรงนี้แล้ว ย้อนไปดูรูปแรกสุดของบทความนี้ ก็น่าจะเห็นภาพมากขึ้นเลยทีเดียวครับ
แต่เพียงแค่ BIP32 เท่านั้นยังไม่พอ จำเป็นต้องเอา BIP ตัวอื่นมาประกอบกันต่อ ถึงจะไปสร้างกระเป๋าเพื่อใช้งานจริงได้ ติดตามเนื้อหากันต่อไปครับ
ขอบคุณสำหรับเนื้อหาครับ