บทที่ 6:Transactions: ธุรกรรมในบิตคอยน์
วิธีที่เรามักใช้ในการโอนเงินสดนั้นแทบไม่เหมือนกับวิธีที่เราโอนบิตคอยน์เลย เงินสดจริงเป็นโทเค็นที่ผู้ใช้ถือครองได้โดยตรง เช่นเมื่ออลิซจ่ายให้บ็อบโดยการยื่นโทเค็นจำนวนหนึ่ง เช่น การส่งธนบัตรดอลลาร์ให้เขา แต่มันไม่ใช่แบบนั้นเลยสำหรับบิตคอยน์ บิตคอยน์ไม่ได้มีอยู่จริงทั้งในรูปแบบทางกายภาพหรือในรูปแบบข้อมูลดิจิทัล อลิซไม่สามารถยื่นบิตคอยน์ให้บ็อบหรือส่งให้ทางอีเมลได้
ลองจินตนการดูว่าหากอลิซต้องการโอนสิทธ์ในการครอบครองที่ดินผืนหนึ่งให้บ๊อบได้อย่างไร เธอไม่สามารถยกที่ดินขึ้นมาแล้วยื่นให้บ็อบได้ แต่จะมีบันทึกบางอย่าง (ที่โดยทั่วไปดูแลโดยหน่วยงานของรัฐท้องถิ่น) ที่ระบุว่าที่ดินผืนนั้นเป็นของอลิซ แปลว่าอลิซสามารถโอนที่ดินให้บ็อบโดยทำให้รัฐบาลอัปเดตบันทึกนั้นให้ระบุว่าบ็อบเป็นเจ้าของที่ดินแทนเธอ
บิตคอยน์ทำงานในลักษณะคล้ายกัน มีฐานข้อมูลของมันอยู่บนโหนดทุก ๆ โหนดของบิตคอยน์ ที่ระบุว่าอลิซควบคุมบิตคอยน์จำนวนหนึ่ง อลิซจ่ายให้บ็อบโดยทำให้โหนด เหล่านั้นอัปเดตฐานข้อมูลของพวกเขาให้ระบุว่าบิตคอยน์บางส่วนของอลิซตอนนี้อยู่ภายใต้การควบคุมของบ็อบ ข้อมูลที่อลิซใช้เพื่อทำให้โหนดอัปเดตฐานข้อมูลของพวกเขาเรียกว่า “ธุรกรรม” ซึ่งกระทำได้โดยไม่ต้องใช้ตัวตนของอลิซหรือบ็อบโดยตรง
ในบทนี้ เราจะมาวิเคราะห์โครงสร้างของธุรกรรมบิตคอยน์และพิจารณาแต่ละส่วนประกอบ เพื่อดูว่ามันเอื้อต่อการโอนมูลค่าได้อย่างไรในรูปแบบที่มีความยืดหยุ่นและน่าเชื่อถือได้อย่างไร
Serialized Bitcoin Transaction: ธุรกรรมบิตคอยน์ในรูปแบบลำดับข้อมูล
ในบทที่ 3 เราได้ใช้ Bitcoin Core โดยเปิดใช้งานตัวเลือก txindex เพื่อดึงสำเนาของธุรกรรมการชำระเงินที่อลิซส่งให้บ็อบ ทีนี้เรามาลองดึงธุรกรรมที่มีการชำระเงินนั้นอีกครั้ง โดยจะแสดงอยู่ในธุรกรรมแบบลำดับข้อมูลของอลิซแทน
$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\
38ecdfdfaa2a8029c1f9bcecd1f96177
01000000000101eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da13569
8679268041c54a0100000000ffffffff02204e0000000000002251203b41daba
4c9ace578369740f15e5ec880c28279ee7f51b07dca69c7061e07068f8240100
000000001600147752c165ea7be772b2c0acb7f4d6047ae6f4768e0141cf5efe
2d8ef13ed0af21d4f4cb82422d6252d70324f6f4576b727b7d918e521c00b51b
e739df2f899c49dc267c0ad280aca6dab0d2fa2b42a45182fc83e81713010000
0000
รูปแบบการลำดับข้อมูลของ Bitcoin Core นั้นมีความพิเศษ เพราะเป็นรูปแบบที่ใช้ในการสร้างคอมมิตเมนต์ของธุรกรรมและส่งต่อธุรกรรมนั้นผ่านเครือข่าย P2P ของบิตคอยน์ อย่างไรก็ตาม โปรแกรมอื่น ๆ สามารถใช้รูปแบบที่ต่างออกไปได้ ตราบใดที่มีการส่งข้อมูลชุดเดียวกันทั้งหมด แต่เนื่องจากรูปแบบของ Bitcoin Core มีความกระทัดรัดเมื่อเทียบกับปริมาณข้อมูลที่ส่ง และยังสามารถแยกวิเคราะห์ได้ง่าย โปรแกรมบิตคอยน์อื่น ๆ จำนวนมากจึงเลือกใช้รูปแบบนี้เช่นกัน
Tip: รูปแบบการลำดับธุรกรรมอีกแบบหนึ่งที่ถูกใช้อย่างแพร่หลายที่เราทราบคือ partially signed bitcoin transaction (PSBT) ซึ่งมีการอธิบายไว้ใน BIP 174 และ BIP 370 (รวมถึงส่วนขยายที่ระบุไว้ใน BIP อื่น ๆ ) PSBT อนุญาตให้โปรแกรมที่ไม่น่าเชื่อถือสร้างเทมเพลตของธุรกรรมขึ้นมา เพื่อให้โปรแกรมที่เชื่อถือได้ (เช่น อุปกรณ์ฮาร์ดแวร์ที่ใช้ลงนามธุรกรรม) ซึ่งถือกุญแจส่วนตัวหรือข้อมูลสำคัญอื่น ๆ ที่จำเป็น สามารถตรวจสอบและเติมข้อมูลในเทมเพลตนั้นได้ เพื่อให้ทำเช่นนี้ได้ PSBT จึงรองรับการจัดเก็บข้อมูลเมตา (metadata) จำนวนมากเกี่ยวกับธุรกรรม ซึ่งทำให้มันมีขนาดใหญ่และไม่กระทัดรัดเท่ารูปแบบการลำดับข้อมูลมาตรฐาน หนังสือเล่มนี้จะไม่ลงรายละเอียดเกี่ยวกับ PSBT แต่เราแนะนำอย่างยิ่งสำหรับนักพัฒนา wallet ที่ต้องการรองรับการเซ็นชื่อด้วยหลายกุญแจ
ธุรกรรมที่แสดงในรูปเลขฐาน 16 ข้างต้น ถูกจำลองเป็นแผนผังไบต์ในรูปข้างล่างนี้ โปรดสังเกตว่าการแสดงข้อมูลขนาด 32 ไบต์ต้องใช้เลขฐานสิบหกจำนวน 64 ตัว แผนผังนี้แสดงเฉพาะฟิลด์ในระดับบนสุดเท่านั้น เราจะมาพิจารณาแต่ละฟิลด์ตามลำดับที่มันปรากฏในธุรกรรม พร้อมอธิบายฟิลด์เพิ่มเติมที่อยู่ภายในแต่ละส่วน

Version
ไบต์สี่ตัวแรกของธุรกรรมบิตคอยน์ในรูปแบบลำดับข้อมูล คือ “เวอร์ชัน” ของมัน เวอร์ชันดั้งเดิมของธุรกรรมบิตคอยน์คือเวอร์ชัน 1 (0x01000000) ธุรกรรมทั้งหมดในระบบบิตคอยน์ต้องปฏิบัติตามกฎของธุรกรรมเวอร์ชัน 1 ซึ่งหลายส่วนของหนังสือเล่มนี้ได้อธิบายกฎเหล่านั้นไว้แล้ว
ธุรกรรมบิตคอยน์เวอร์ชัน 2 ถูกนำมาใช้ครั้งแรกในการอัปเกรดแบบซอฟต์ฟอร์กตามข้อเสนอ BIP68 ซึ่งเพิ่มข้อจำกัดเพิ่มเติมกับฟิลด์ sequence แต่ข้อจำกัดนี้จะมีผลเฉพาะกับธุรกรรมเวอร์ชัน 2 ขึ้นไปเท่านั้น ส่วนธุรกรรมเวอร์ชัน 1 จะไม่ได้รับผลกระทบ ข้อเสนอ BIP112 ซึ่งเป็นส่วนหนึ่งของซอฟต์ฟอร์กเดียวกันกับ BIP68 ได้อัปเกรดคำสั่ง OP_CHECKSEQUENCEVERIFY โดยทำให้คำสั่งนี้ล้มเหลวหากถูกประเมินในธุรกรรมที่มีเวอร์ชันต่ำกว่า 2 นอกเหนือจากการเปลี่ยนแปลงสองจุดนี้ ธุรกรรมเวอร์ชัน 2 จะเหมือนกับเวอร์ชัน 1 ทุกประการ
Protecting Presigned Transactions
ขั้นตอนสุดท้ายก่อนการกระจายธุรกรรมเข้าสู่เครือข่ายเพื่อบันทึกลงในบล็อกเชนคือ “การลงนาม” ธุรกรรม อย่างไรก็ตาม สามารถลงนามในธุรกรรมได้โดยยังไม่ต้องกระจายออกไปทันที คุณสามารถเก็บธุรกรรมที่ลงนามล่วงหน้าไว้นานหลายเดือนหรือหลายปี โดยเชื่อว่าจะสามารถนำมาบันทึกลงบล็อกเชนได้ในภายหลังเมื่อมีการกระจายธุรกรรมนั้น ในระหว่างนั้นคุณอาจสูญเสียการเข้าถึง private key ที่จำเป็นสำหรับการเซ็นธุรกรรมอื่นเพื่อใช้จ่ายเงินเดียวกันได้ ปรากฏการณ์นี้ไม่ใช่เรื่องสมมติ โปรโตคอลหลายอย่างที่สร้างบนบิตคอยน์ เช่น Lightning Network พึ่งพาธุรกรรมที่ลงนามล่วงหน้าเหล่านี้
สิ่งนี้สร้างความท้าทายให้กับนักพัฒนาโปรโตคอล เมื่อพวกเขาต้องช่วยผู้ใช้อัปเกรดโปรโตคอลฉันทามติของบิตคอยน์ การเพิ่มข้อจำกัดใหม่ เช่นที่ BIP68 เพิ่มไว้ในฟิลด์ sequence อาจทำให้ธุรกรรมที่ลงนามล่วงหน้าบางรายการกลายเป็นโมฆะ และหากไม่สามารถสร้างลายเซ็นใหม่สำหรับธุรกรรมที่เทียบเท่ากันได้ เงินในธุรกรรมเหล่านั้นก็จะสูญหายไปอย่างถาวร
ปัญหานี้ได้รับการแก้ไขโดยการ “สงวน” ฟีเจอร์บางอย่างของธุรกรรมไว้สำหรับการอัปเกรดในอนาคต เช่น หมายเลขเวอร์ชัน ผู้ที่สร้างธุรกรรมแบบลงนามล่วงหน้าก่อน BIP68 ควรใช้ธุรกรรมเวอร์ชัน 1 ดังนั้นการที่ BIP68 กำหนดข้อจำกัดเพิ่มเติมให้มีผลเฉพาะกับธุรกรรมเวอร์ชัน 2 ขึ้นไป จึงไม่ทำให้ธุรกรรมที่ลงนามไว้ก่อนหน้านั้นเป็นโมฆะ
หากคุณกำลังพัฒนาโปรโตคอลที่ใช้ธุรกรรมแบบลงนามล่วงหน้า ให้แน่ใจว่าโปรโตคอลของคุณไม่ได้ใช้ฟีเจอร์ที่ถูกสงวนไว้สำหรับการอัปเกรดในอนาคต นโยบายการส่งต่อธุรกรรม (relay policy) เริ่มต้นของ Bitcoin Core ไม่อนุญาตให้ใช้ฟีเจอร์ที่ถูกสงวนเหล่านี้ คุณสามารถทดสอบว่าธุรกรรมของคุณสอดคล้องกับนโยบายดังกล่าวหรือไม่ โดยใช้คำสั่ง RPC testmempoolaccept บน Bitcoin mainnet
ณ เวลาที่เขียนนี้ มีข้อเสนอให้เริ่มใช้ธุรกรรมเวอร์ชัน 3 อย่างแพร่หลาย ข้อเสนอนี้ไม่ได้เปลี่ยนแปลงกฎฉันทามติของบิตคอยน์ แต่เปลี่ยนเฉพาะนโยบายที่โหนดใช้ในการส่งต่อธุรกรรม โดยภายใต้ข้อเสนอนั้น ธุรกรรมเวอร์ชัน 3 จะถูกกำหนดข้อจำกัดเพิ่มเติม เพื่อป้องกันการโจมตีแบบ Denial of Service (DoS) บางรูปแบบ ซึ่งเราจะกล่าวถึงเพิ่มเติมในส่วนถัดไป
Extended Marker and Flag: ตัวระบุ Marker และ Flag แบบขยาย
ฟิลด์สองช่องถัดไปของตัวอย่างธุรกรรมในรูปแบบลำดับข้อมูล ถูกเพิ่มเข้ามาในกระบวนการอัปเกรดแบบ soft fork ของ Segregated Witness (SegWit) ซึ่งเป็นการเปลี่ยนแปลงในกฎฉันทามติของบิตคอยน์ การเปลี่ยนแปลงนี้อ้างอิงตามข้อเสนอ BIP141 และ BIP143 โดยที่รูปแบบการลำดับข้อมูลแบบขยายนี้ถูกกำหนดไว้ใน BIP144
หากธุรกรรมมีโครงสร้างของ witness (ซึ่งเราจะอธิบายในหัวข้อ “Witness Structure”) ค่า marker ต้องเป็นศูนย์ (0x00) และค่า flag ต้องไม่เป็นศูนย์ ในโปรโตคอล P2P ปัจจุบัน ค่าของ flag ควรเป็นหนึ่ง (0x01) เสมอ โดยมีการสงวนค่าอื่นไว้สำหรับการอัปเกรดโปรโตคอลในอนาคต
หากธุรกรรมไม่มีข้อมูล witness stack ค่า marker และ flag จะต้องไม่ปรากฏอยู่ในธุรกรรม ซึ่งทำให้สามารถทำงานร่วมกับรูปแบบการลำดับข้อมูลดั้งเดิมของบิตคอยน์ได้ หรือที่ปัจจุบันเรียกว่า legacy serialization (รายละเอียดเพิ่มเติมดูได้ในหัวข้อ “Legacy Serialization”)
ในรูปแบบ legacy การตีความค่า marker จะถือว่าเป็นจำนวนของอินพุต (ซึ่งในกรณีนี้คือศูนย์) แต่ธุรกรรมไม่สามารถมีอินพุตเป็นศูนย์ได้ ดังนั้น marker จึงทำหน้าที่เป็นสัญญาณให้โปรแกรมรุ่นใหม่รู้ว่า ธุรกรรมนี้ใช้รูปแบบการลำดับข้อมูลแบบขยาย ส่วนฟิลด์ flag ก็ทำหน้าที่ในลักษณะเดียวกัน และยังช่วยให้การอัปเดตรูปแบบการลำดับข้อมูลในอนาคตเป็นไปได้ง่ายขึ้นอีกด้วย
Inputs
ฟิลด์ inputs ประกอบด้วยฟิลด์ย่อยหลายส่วน ดังนั้นเราจะมาเริ่มกันจากการดูแผนผังไบต์ของฟิลด์นี้ในตัวอย่างธุรกรรมของอลิซ ที่แสดงไว้ในภาพด้านล่าง

Length of Transaction Input List: ความยาวของรายการอินพุตของธุรกรรม
รายการอินพุตของธุรกรรมจะเริ่มต้นด้วยจำนวนเต็มที่ระบุจำนวนอินพุตทั้งหมดในธุรกรรมนั้น ค่าต่ำสุดคือหนึ่ง และแม้จะไม่มีการกำหนดค่าสูงสุดอย่างชัดเจน แต่ข้อจำกัดของขนาดธุรกรรมสูงสุดในบิตคอยน์จะจำกัดให้ธุรกรรมหนึ่งมีได้เพียงไม่กี่พันอินพุตเท่านั้น ตัวเลขนี้จะถูกเข้ารหัสในรูปแบบ compactSize unsigned integer
CompactSize Unsigned Integers: จำนวนเต็มแบบ CompactSize
จำนวนเต็มที่ไม่มีเครื่องหมาย (unsigned integers) ในบิตคอยน์ ซึ่งโดยทั่วไปมักมีค่าต่ำ แต่บางครั้งอาจมีค่าสูง จะถูกเข้ารหัสโดยใช้ชนิดข้อมูลที่เรียกว่า compactSize ซึ่งเป็นรูปแบบของจำนวนเต็มแบบความยาวแปรผัน (variable-length integer) จึงมักถูกเรียกว่า var_int หรือ varint (ดูรายละเอียดเพิ่มเติมได้ในเอกสารของ BIP37 และ BIP144)
warning: มีรูปแบบของ “จำนวนเต็มแบบความยาวแปรผัน” (variable-length integers) หลายแบบที่ถูกใช้ในโปรแกรมต่าง ๆ รวมถึงโปรแกรมในระบบบิตคอยน์เองด้วย ตัวอย่างเช่น Bitcoin Core ใช้ชนิดข้อมูลที่เรียกว่า VarInts ในการจัดเก็บฐานข้อมูล UTXO ซึ่ง แตกต่างจาก compactSize นอกจากนี้ ฟิลด์ nBits ในส่วนหัวของบล็อก (block header) ก็ถูกเข้ารหัสด้วยชนิดข้อมูลเฉพาะที่เรียกว่า Compact ซึ่งก็ไม่เกี่ยวข้องกับ compactSize เช่นกัน ดังนั้น เมื่อเราอ้างถึงจำนวนเต็มแบบความยาวแปรผันที่ใช้ในการ serialize ธุรกรรมบิตคอยน์ (Bitcoin transaction) และในส่วนอื่นของ โปรโตคอล P2P ของบิตคอยน์ เราจะใช้คำเต็มว่า compactSize เสมอ เพื่อป้องกันความสับสนกับรูปแบบอื่น ๆ ที่ชื่อคล้ายกันแต่มีโครงสร้างต่างกัน
สำหรับตัวเลขในช่วง 0 ถึง 252 ค่าของ compactSize unsigned integers จะเหมือนกับชนิดข้อมูล uint8_t ในภาษา C ทุกประการ ซึ่งเป็นรูปแบบการเข้ารหัสตัวเลขที่โปรแกรมเมอร์ส่วนใหญ่คุ้นเคยอยู่แล้ว แต่สำหรับตัวเลขที่มีค่ามากกว่า 252 (จนถึง 0xffffffffffffffff) จะมีการเพิ่ม ไบต์นำหน้า (prefix byte) เพื่อระบุความยาวของจำนวนตัวเลขนั้น อย่างไรก็ตาม นอกจากไบต์นำหน้าแล้ว ตัวเลขส่วนที่เหลือจะยังคงมีลักษณะเหมือนกับการเข้ารหัสของจำนวนเต็มแบบ unsigned ปกติในภาษา C อยู่ดี
| ค่าตัวเลข (Value) | จำนวนไบต์ที่ใช้ (Bytes used) | รูปแบบ (Format) | | :---- | ----- | ----- | | ≥ 0 และ ≤ 252 (0xfc) | 1 | เข้ารหัสเป็น uint8_t | | ≥ 253 และ ≤ 0xffff | 3 | เริ่มด้วย 0xfd ตามด้วยตัวเลขที่เข้ารหัสเป็น uint16_t | | ≥ 0x10000 และ ≤ 0xffffffff | 5 | เริ่มด้วย 0xfe ตามด้วยตัวเลขที่เข้ารหัสเป็น uint32_t | | ≥ 0x100000000 และ ≤ 0xffffffffffffffff | 9 | เริ่มด้วย 0xff ตามด้วยตัวเลขที่เข้ารหัสเป็น uint64_t |
อินพุตแต่ละรายการในธุรกรรมจะต้องมีสามฟิลด์หลัก ได้แก่ Outpoint field, Length-prefixed input script field, และ Sequence
เราจะพิจารณาแต่ละฟิลด์เหล่านี้ในส่วนถัดไป อินพุตบางรายการยังมี witness stack ด้วย แต่จะถูกจัดลำดับข้อมูลไว้ที่ท้ายธุรกรรม ดังนั้นเราจะมาศึกษามันในภายหลัง
Outpoint
ธุรกรรมบิตคอยน์คือคำขอที่ส่งให้ full node ทำการอัปเดตฐานข้อมูลเกี่ยวกับข้อมูลความเป็นเจ้าของเหรียญ สำหรับอลิซที่จะโอนบิตคอยน์บางส่วนของเธอให้บ๊อบ เธอต้องบอกให้โหนดรู้ก่อนว่าจะหาธุรกรรมก่อนหน้าที่เธอได้รับบิตคอยน์เหล่านั้นได้จากที่ไหน เนื่องจากการควบคุมบิตคอยน์ถูกกำหนดไว้ใน output ของธุรกรรม อลิซจึงชี้ไปยัง output ก่อนหน้าโดยใช้ฟิลด์ outpoint อินพุตแต่ละรายการต้องมี outpoint หนึ่งรายการเสมอ
outpoint ประกอบด้วย txid ขนาด 32 ไบต์ของธุรกรรมที่อลิซได้รับบิตคอยน์ที่เธอต้องการใช้จ่าย txid นี้อยู่ในลำดับไบต์ภายในของบิตคอยน์สำหรับแฮช (ดูหัวข้อ Internal and Display Byte Orders)
เนื่องจากธุรกรรมหนึ่งรายการอาจมีหลาย output อลิซจึงต้องระบุด้วยว่าเธอจะใช้ output ใดจากธุรกรรมนั้น ซึ่งเรียกว่า output index โดย output index เป็นจำนวนเต็มแบบไม่ติดลบ (unsigned integer) ขนาด 4 ไบต์ เริ่มจากศูนย์
เมื่อ full node พบ outpoint มันจะใช้ข้อมูลนี้เพื่อค้นหา output ที่ถูกอ้างอิง โหนดเต็มจะต้องค้นหาเฉพาะธุรกรรมก่อนหน้าในบล็อกเชนเท่านั้น ตัวอย่างเช่น หากธุรกรรมของอลิซถูกบันทึกไว้ในบล็อก 774,958 full node ที่ตรวจสอบธุรกรรมของเธอจะค้นหา output ที่ถูกอ้างอิงในบล็อกนั้นและบล็อกก่อนหน้าเท่านั้น ไม่ใช่บล็อกที่อยู่หลังจากนั้น ภายในบล็อก 774,958 full node จะค้นหาเฉพาะธุรกรรมที่อยู่ก่อนธุรกรรมของอลิซ โดยพิจารณาตามลำดับของ leaf ใน merkle tree ของบล็อกนั้น
เมื่อพบ output ก่อนหน้าแล้ว full node จะได้รับข้อมูลสำคัญหลายอย่างจากมัน ได้แก่
-
จำนวนบิตคอยน์ที่ถูกกำหนดไว้ใน output ก่อนหน้า: บิตคอยน์ทั้งหมดใน output นั้นจะถูกโอนไปในธุรกรรมนี้ ตัวอย่างเช่น ในธุรกรรมตัวอย่าง ค่าใน output ก่อนหน้าคือ 100,000 satoshis
-
เงื่อนไขการอนุญาตของ output ก่อนหน้า: เงื่อนไขที่ต้องถูกปฏิบัติตามเพื่อที่จะใช้จ่ายบิตคอยน์ที่ถูกกำหนดไว้ใน output นั้น
-
สำหรับธุรกรรมที่ได้รับการยืนยันแล้ว (confirmed transactions) full node จะทราบ block height ที่ธุรกรรมได้รับการยืนยัน และ ค่า median time past (MTP) ของบล็อกนั้น ข้อมูลนี้จำเป็นสำหรับการทำงานของ relative timelock (อธิบายไว้ในหัวข้อ Sequence as a consensus-enforced relative timelock) และสำหรับ output ของ coinbase transaction (อธิบายไว้ในหัวข้อ Coinbase Transactions)
-
หลักฐานว่า output ก่อนหน้านั้นมีอยู่จริงในบล็อกเชน (หรือเป็นธุรกรรมที่ยังไม่ได้ยืนยันแต่เป็นที่รู้จัก) และยังไม่มีธุรกรรมอื่นใดที่ใช้มันไปแล้ว หนึ่งในกฎฉันทามติของบิตคอยน์ห้ามไม่ให้ output เดียวกันถูกใช้มากกว่าหนึ่งครั้งภายในบล็อกเชน นี่คือกฎที่ป้องกัน double spending อลิซไม่สามารถใช้ output เดิมเดียวกันเพื่อจ่ายทั้งให้บ๊อบและแครอลในธุรกรรมที่แยกกันได้ สองธุรกรรมที่พยายามใช้ output เดียวกันเรียกว่า conflicting transactions เพราะมีเพียงหนึ่งในนั้นเท่านั้นที่สามารถถูกบันทึกในบล็อกเชนได้
แนวทางที่แตกต่างกันในการติดตาม output ก่อนหน้าได้ถูกทดลองใช้โดยการทำงานของ full node ที่แตกต่างกันในช่วงเวลาต่าง ๆ ส่วนในปัจจุบัน Bitcoin Core ใช้วิธีที่เชื่อว่าได้ผลดีที่สุดในการเก็บรักษาข้อมูลที่จำเป็นทั้งหมดในขณะที่ลดการใช้พื้นที่ดิสก์ให้น้อยที่สุด คือมันเก็บฐานข้อมูลที่บันทึกทุก UTXO และเมตาดาต้าที่จำเป็นเกี่ยวกับมัน (เช่น block height) ทุกครั้งที่บล็อกใหม่ของธุรกรรมเข้ามา output ทั้งหมดที่ถูกใช้จะถูกลบออกจากฐานข้อมูล UTXO และ output ทั้งหมดที่ถูกสร้างขึ้นใหม่จะถูกเพิ่มเข้าไปในฐานข้อมูล
Internal and Display Byte Orders(การจัดเรียงไบต์ภายในและสำหรับการแสดงผล)
บิตคอยน์ได้ใช้ hash function ที่เรียกว่า digest ในหลายรูปแบบ Digest ถูกใช้เป็นตัวระบุเฉพาะสำหรับบล็อกและธุรกรรม ใช้ในกระบวนการยืนยันความถูกต้องของ address บล็อก ธุรกรรม ลายเซ็น และอื่น ๆ อีกมากมาย นอกจากนี้ digest ยังถูกนำมาใช้ซ้ำในฟังก์ชัน proof-of-work ของ Bitcoin ในบางกรณี digest ของ hash จะถูกแสดงให้ผู้ใช้เห็นในรูปแบบการจัดเรียงไบต์หนึ่ง แต่ระบบภายในกลับใช้รูปแบบการจัดเรียงไบต์อีกแบบหนึ่ง ซึ่งอาจก่อให้เกิดความสับสนได้ ตัวอย่างเช่น ลองพิจารณา txid ของ output ก่อนหน้าใน outpoint ของธุรกรรมตัวอย่างของเรา
eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a
ถ้าเราพยายามใช้ txid นั้นเพื่อดึงข้อมูลธุรกรรมจาก Bitcoin Core เราจะพบข้อผิดพลาด และจำเป็นต้องสลับลำดับไบต์ของมันก่อน
$ bitcoin-cli getrawtransaction \
eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a
error code: -5
error message:
No such mempool or blockchain transaction.
Use gettransaction for wallet transactions.
$ echo eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a \
| fold -w2 | tac | tr -d "\n"
4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19278fe33aeb
$ bitcoin-cli getrawtransaction \
4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19278fe33aeb
02000000000101c25ae90c9f3d40cc1fc509ecfd54b06e35450702…
พฤติกรรมแปลก ๆ นี้น่าจะเป็นผลที่เกิดขึ้นโดยไม่ได้ตั้งใจจากการตัดสินใจออกแบบในซอฟต์แวร์บิตคอยน์ยุคแรก ๆ ในทางปฏิบัติ นั่นหมายความว่านักพัฒนาซอฟต์แวร์บิตคอยน์จำเป็นต้องจำไว้ว่าต้องสลับลำดับไบต์ในตัวระบุธุรกรรมและบล็อกก่อนที่จะแสดงให้ผู้ใช้เห็น
ในหนังสือเล่มนี้ เราใช้คำว่า ลำดับไบต์ภายใน (internal byte order) สำหรับข้อมูลที่ปรากฏอยู่ภายในธุรกรรมและบล็อก และใช้คำว่า ลำดับไบต์สำหรับแสดงผล (display byte order) สำหรับรูปแบบที่แสดงต่อผู้ใช้ อีกชุดของคำที่มักใช้กันคือ ลำดับไบต์แบบ little-endian สำหรับรูปแบบภายใน และ ลำดับไบต์แบบ big-endian สำหรับรูปแบบที่แสดงผล
อันนี้หลามเสริมให้ มันคือการแบ่งทีละสองตำแหน่งและนับว่านั่นเป็นหนึ่ง เช่น abcdef ก็จะเป็น ab คือ 1 cd คือ 2 ef คือ 3 พอแปลงเป็น display byte order ก็ทำการสลับเป็น 3,2,1 หรือ efcdab
Input Script
ฟิลด์ input script เป็นส่วนที่หลงเหลือมาจากรูปแบบธุรกรรมแบบดั้งเดิม (legacy transaction format) ตัวอย่างธุรกรรมของเราเป็น input ที่ใช้จ่าย output แบบ native segwit ซึ่งไม่จำเป็นต้องมีข้อมูลใน input script ดังนั้นค่านำหน้าความยาว (length prefix) ของ input script จึงถูกตั้งค่าเป็นศูนย์ (0x00)
สำหรับตัวอย่างของ input script ที่มีค่านำหน้าความยาวและใช้จ่าย output แบบดั้งเดิม เราจะใช้ตัวอย่างจากธุรกรรมหนึ่งในบล็อกล่าสุดในขณะที่เขียนอยู่นี้
6b483045022100a6cc4e8cd0847951a71fad3bc9b14f24d44ba59d19094e0a8c
fa2580bb664b020220366060ea8203d766722ed0a02d1599b99d3c95b97dab8e
41d3e4d3fe33a5706201210369e03e2c91f0badec46c9c903d9e9edae67c167b
9ef9b550356ee791c9a40896
ค่านำหน้าความยาว (length prefix) เป็นจำนวนเต็มแบบ compactSize unsigned integer ที่ระบุความยาวของฟิลด์ input script ที่ถูกซีเรียลไลซ์ไว้ ในกรณีนี้เป็นไบต์เดียว (0x6b) ซึ่งระบุว่า input script มีความยาว 107 ไบต์
Sequence(ลำดับ)
ไบต์สี่ตัวสุดท้ายของ input คือหมายเลขลำดับ (sequence number) ซึ่งการใช้งานและความหมายของฟิลด์นี้ได้มีการเปลี่ยนแปลงไปตามกาลเวลา
การแทนที่ธุรกรรมโดยอิงตามลำดับ (Original sequence-based transaction replacement)
เดิมทีฟิลด์ sequence ถูกออกแบบมาเพื่อให้สามารถสร้างหลายเวอร์ชันของธุรกรรมเดียวกันได้ โดยที่เวอร์ชันหลังสามารถแทนที่เวอร์ชันก่อนหน้าในฐานะตัวเลือกสำหรับการยืนยัน หมายเลขลำดับทำหน้าที่ติดตามเวอร์ชันของธุรกรรม
ตัวอย่างเช่น ลองจินตนาการว่าอลิซและบ็อบต้องการเดิมพันผลเกมไพ่ พวกเขาเริ่มจากการลงนามธุรกรรมที่แต่ละคนฝากเงินไว้ในเอาต์พุตซึ่งต้องการลายเซ็นจากทั้งคู่เพื่อใช้จ่าย ซึ่งเรียกว่า สคริปต์หลายลายเซ็น (multisignature script หรือ multisig) ธุรกรรมนี้เรียกว่า setup transaction จากนั้นพวกเขาสร้างธุรกรรมที่ใช้จ่ายเอาต์พุตนั้นดังนี้
- เวอร์ชันแรกของธุรกรรม โดยมีค่า
nSequenceเท่ากับ 0 (0x00000000) เป็นการคืนเงินให้อลิซและบ็อบตามจำนวนที่แต่ละคนฝากไว้ในตอนแรก ธุรกรรมนี้เรียกว่า refund transaction ซึ่งทั้งคู่ยังไม่เผยแพร่ในตอนนี้ จะใช้ก็ต่อเมื่อเกิดปัญหาเท่านั้น - อลิซชนะรอบแรกของเกมไพ่ ธุรกรรมเวอร์ชันที่สองมีค่า sequence เป็น 1 และปรับจำนวนเงินที่อลิซได้รับให้มากขึ้น ในขณะที่ส่วนของบ็อบลดลง ทั้งคู่ลงนามในธุรกรรมที่อัปเดตแล้วนี้อีกครั้ง แต่ก็ยังไม่เผยแพร่ เว้นแต่จะมีเหตุจำเป็น
- บ็อบชนะรอบที่สอง ลำดับจึงถูกเพิ่มเป็น 2 ส่วนแบ่งของอลิซลดลงและของบ็อบเพิ่มขึ้น ทั้งคู่ลงนามอีกครั้งแต่ยังไม่เผยแพร่
หลังจากเล่นอีกหลายรอบ โดยที่ค่า sequence ถูกเพิ่มขึ้นในแต่ละรอบ เงินถูกปรับสมดุลใหม่ และธุรกรรมถูกลงนามแต่ยังไม่เผยแพร่ พวกเขาตัดสินใจจะสิ้นสุดเกม ด้วยการสร้างธุรกรรมสุดท้ายที่สะท้อนยอดเงินสุดท้ายของทั้งคู่ จากนั้นตั้งค่า sequence เป็นค่าสูงสุด (0xffffffff) เพื่อเป็นการ “สิ้นสุด” ธุรกรรมนี้ พวกเขาจึงเผยแพร่เวอร์ชันนี้ออกไป ธุรกรรมถูกส่งต่อไปทั่วเครือข่าย และในที่สุดก็ได้รับการยืนยันโดยนักขุด
เราสามารถเห็นกฎการแทนที่ของ sequence ทำงานอย่างไรได้ หากพิจารณาสถานการณ์ทางเลือกดังต่อไปนี้
-
ลองจินตนาการว่าอลิซเผยแพร่ธุรกรรมสุดท้ายที่มีค่า sequence เท่ากับ 0xffffffff จากนั้นบ็อบเผยแพร่ธุรกรรมเวอร์ชันก่อนหน้าที่เขาได้รับส่วนแบ่งมากกว่า เนื่องจากเวอร์ชันของบ็อบมีหมายเลขลำดับต่ำกว่า โหนดเต็ม (full nodes) ที่ใช้โค้ดบิตคอยน์เวอร์ชันดั้งเดิมจะไม่ส่งต่อธุรกรรมนั้นไปยังนักขุด และนักขุดที่ใช้โค้ดเวอร์ชันเดิมก็จะไม่ขุดมันเช่นกัน
-
ในอีกสถานการณ์หนึ่ง สมมติว่าบ็อบเผยแพร่ธุรกรรมเวอร์ชันก่อนหน้าเพียงไม่กี่วินาทีก่อนที่อลิซจะเผยแพร่เวอร์ชันสุดท้าย โหนดต่าง ๆ จะส่งต่อเวอร์ชันของบ็อบและนักขุดจะพยายามขุดมัน แต่เมื่อเวอร์ชันของอลิซที่มีหมายเลขลำดับสูงกว่ามาถึง โหนดก็จะส่งต่อเวอร์ชันของอลิซด้วย และนักขุดที่ใช้โค้ดบิตคอยน์เวอร์ชันดั้งเดิมจะพยายามขุดเวอร์ชันของอลิซแทนเวอร์ชันของบ็อบ เว้นแต่ว่าบ็อบจะโชคดีและมีการค้นพบบล็อกก่อนที่เวอร์ชันของอลิซจะมาถึง มิฉะนั้นธุรกรรมเวอร์ชันของอลิซจะเป็นเวอร์ชันที่ได้รับการยืนยันในที่สุด
โปรโตคอลประเภทนี้คือสิ่งที่เราเรียกว่า payment channel ในปัจจุบัน ผู้สร้างบิตคอยน์ได้กล่าวถึงในอีเมลที่มีการอ้างถึงเขา เรียกธุรกรรมประเภทนี้ว่า high-frequency transactions และได้อธิบายคุณสมบัติบางอย่างที่เพิ่มเข้าไปในโปรโตคอลเพื่อรองรับการใช้งานเหล่านี้ เราจะเรียนรู้เกี่ยวกับคุณสมบัติอื่น ๆ เหล่านี้ในภายหลัง และยังจะได้เห็นด้วยว่าเวอร์ชันสมัยใหม่ของ payment channel ถูกนำมาใช้ในบิตคอยน์อย่างแพร่หลายมากขึ้นในปัจจุบัน
มีปัญหาบางประการกับ payment channel ที่อิงตาม sequence เพียงอย่างเดียว ปัญหาแรกคือกฎสำหรับการแทนที่ธุรกรรมที่มี sequence ต่ำด้วยธุรกรรมที่มี sequence สูงเป็นเพียงนโยบายของซอฟต์แวร์เท่านั้น ไม่มีแรงจูงใจโดยตรงให้นักขุดชอบเวอร์ชันใดเวอร์ชันหนึ่งมากกว่าเวอร์ชันอื่น ปัญหาที่สองคือคนแรกที่ส่งธุรกรรมอาจโชคดีให้ธุรกรรมนั้นได้รับการยืนยัน แม้ว่าจะไม่ใช่ธุรกรรมที่มี sequence สูงสุดก็ตาม โปรโตคอลด้านความปลอดภัยที่ล้มเหลวบ้างเพียงไม่กี่เปอร์เซ็นต์เพราะโชคไม่ดีจึงไม่ใช่โปรโตคอลที่มีประสิทธิภาพนัก
ปัญหาที่สามคือสามารถแทนที่เวอร์ชันหนึ่งของธุรกรรมด้วยเวอร์ชันที่ต่างออกไปได้โดยไม่จำกัดจำนวนครั้ง แต่ละครั้งที่แทนที่จะใช้แบนด์วิดท์ของโหนดเต็มที่ทำหน้าที่ส่งต่อบนเครือข่ายทั้งหมด ยกตัวอย่างเช่น ณ เวลาที่เขียนนี้ มี full node ที่ทำหน้าที่ส่งต่อประมาณ 50,000 โหนด; ผู้โจมตีที่สร้างธุรกรรมแทนที่ 1,000 รายการต่อหนึ่งนาที โดยแต่ละรายการมีขนาด 200 ไบต์ จะใช้แบนด์วิดท์ส่วนตัวของเขาประมาณ 20 KB ต่อหนึ่งนาที แต่จะใช้แบนด์วิดท์เครือข่ายของ full node ประมาณ 10 GB ทุก ๆ นาที นอกจากค่าใช้จ่ายแบนด์วิดท์ส่วนตัว 20 KB/นาที และค่าธรรมเนียมเพียงเป็นครั้งคราวเมื่อต้องมีธุรกรรมที่ถูกยืนยัน ผู้โจมตีจะไม่ต้องจ่ายค่าใช้จ่ายใด ๆ สำหรับภาระอันมหาศาลที่พวกเขาก่อขึ้นกับผู้ดำเนิน full node เลย
เพื่อกำจัดความเสี่ยงจากการโจมตีนี้ รูปแบบการแทนที่ธุรกรรมที่อิงตาม sequence แบบเดิมถูกปิดใช้งานในเวอร์ชันแรก ๆ ของซอฟต์แวร์บิตคอยน์ ในช่วงหลายปี full node ของบิตคอยน์จะไม่อนุญาตให้ธุรกรรมที่ยังไม่ได้ยืนยันซึ่งมีอินพุตหนึ่ง (ตามที่ระบุโดย outpoint) ถูกแทนที่ด้วยธุรกรรมอื่นที่มีอินพุตเดียวกัน อย่างไรก็ตาม สถานการณ์นั้นไม่ได้ดำเนินต่อไปตลอดเวลา
การสัญญาณเพื่อแทนที่ธุรกรรมแบบ Opt-in
หลังจากรูปแบบการแทนที่ธุรกรรมที่อิงตาม sequence แบบเดิมถูกปิดใช้งานเนื่องจากมีความเสี่ยงที่จะถูกละเมิด ได้มีการเสนอวิธีแก้ไข: การโปรแกรม Bitcoin Core และซอฟต์แวร์โหนดเต็มที่ส่งต่อธุรกรรมอื่น ๆ ให้อนุญาตให้ธุรกรรมที่จ่ายค่าธรรมเนียมสูงแทนที่ธุรกรรมที่ขัดแย้งและจ่ายค่าธรรมเนียมต่ำกว่า เรียกวิธีนี้ว่า replace by fee (RBF)
ผู้ใช้และธุรกิจบางรายคัดค้านการเพิ่มการสนับสนุนการแทนที่ธุรกรรมกลับเข้าไปใน Bitcoin Core ดังนั้นจึงได้ข้อยุติด้วยการใช้ฟิลด์ sequence อีกครั้งเพื่อสนับสนุนการแทนที่
ตามที่ระบุใน BIP125 ธุรกรรมที่ยังไม่ได้ยืนยันซึ่งมีอินพุตใด ๆ ที่ตั้งค่า sequence ต่ำกว่า 0xfffffffe (เช่น อย่างน้อย 2 ต่ำกว่าค่าสูงสุด) จะส่งสัญญาณไปยังเครือข่ายว่าผู้ลงนามต้องการให้ธุรกรรมนี้สามารถถูกแทนที่ด้วยธุรกรรมที่จ่ายค่าธรรมเนียมสูงกว่าได้ Bitcoin Core อนุญาตให้ธุรกรรมที่ยังไม่ได้ยืนยันเหล่านี้ถูกแทนที่ได้ และยังคงไม่อนุญาตให้ธุรกรรมอื่น ๆ ถูกแทนที่ ทำให้ผู้ใช้และธุรกิจที่คัดค้านการแทนที่สามารถเพิกเฉยต่อธุรกรรมที่ยังไม่ได้ยืนยันซึ่งมีสัญญาณ BIP125 จนกว่าจะได้รับการยืนยัน
นโยบายการแทนที่ธุรกรรมสมัยใหม่มีมากกว่าการพิจารณาค่าธรรมเนียมและสัญญาณ sequence ซึ่งเราจะเห็นรายละเอียดเพิ่มเติมในหัวข้อ rbf
Sequence ในฐานะกลไกกำหนดเวลาล็อกแบบสัมพัทธ์ (relative timelock) ที่ถูกบังคับโดยฉันทามติ
ในหัวข้อ Version เราได้เรียนรู้ว่า soft fork BIP68 ได้เพิ่มข้อจำกัดใหม่ให้กับธุรกรรมที่มีหมายเลขเวอร์ชัน 2 ขึ้นไป ซึ่งข้อจำกัดนั้นถูกใช้กับฟิลด์ sequence
อินพุตของธุรกรรมที่มีค่า sequence น้อยกว่า 2³¹ จะถูกตีความว่ามีการตั้ง relative timelock และธุรกรรมดังกล่าวจะสามารถถูกบรรจุเข้าไปในบล็อกเชนได้ก็ต่อเมื่อเอาต์พุตก่อนหน้า (ที่ถูกอ้างอิงโดย outpoint) ผ่านระยะเวลาเท่ากับค่าของ relative timelock มาแล้ว ตัวอย่างเช่น ธุรกรรมที่มีอินพุตหนึ่งตัวตั้ง relative timelock ไว้ 30 บล็อก จะสามารถถูกยืนยันในบล็อกที่มีอย่างน้อย 29 บล็อกคั่นอยู่ระหว่างบล็อกนั้นกับบล็อกที่มีเอาต์พุตซึ่งกำลังถูกใช้จ่ายอยู่บนบล็อกเชนเดียวกัน เนื่องจาก sequence เป็นฟิลด์ที่ผูกกับแต่ละอินพุต ธุรกรรมหนึ่งรายการอาจมีอินพุตหลายตัวที่มีการตั้ง timelock และอินพุตทั้งหมดต้องผ่านช่วงเวลาตามที่กำหนดแล้วจึงทำให้ธุรกรรมถือว่าถูกต้อง มี disable flag ที่อนุญาตให้ธุรกรรมสามารถรวมอินพุตแบบมี relative timelock (sequence < 2³¹) และอินพุตที่ไม่มี relative timelock (sequence ≥ 2³¹) ไว้ในธุรกรรมเดียวกันได้
ค่า sequence สามารถกำหนดเป็นหน่วยบล็อกหรือหน่วยวินาทีได้ โดยมี type-flag ใช้แยกความแตกต่างระหว่างค่าที่นับเป็นบล็อกกับค่าที่นับเป็นวินาที type-flag จะถูกตั้งค่าไว้ที่บิตลำดับที่ 23 (กล่าวคือค่า 1<<22) หาก type-flag ถูกตั้งค่า ค่า sequence จะถูกตีความเป็นจำนวนเท่าของ 512 วินาที แต่ถ้า type-flag ไม่ได้ถูกตั้งค่า ค่า sequence จะถูกตีความเป็นจำนวนบล็อก เมื่อทำการตีความ sequence เป็น relative timelock จะพิจารณาเฉพาะ 16 บิตที่มีค่าน้อยที่สุดเท่านั้น เมื่อทำการประเมินค่าสถานะของ flags แล้ว (บิต 32 และบิต 23) ค่า sequence จะถูก “mask” ด้วย mask ขนาด 16 บิต (เช่น sequence & 0x0000FFFF) ค่าหนึ่งเท่าของ 512 วินาทีมีค่าประมาณเท่ากับเวลาเฉลี่ยระหว่างบล็อก ดังนั้นค่า relative timelock สูงสุดจาก 16 บิต (2¹⁶) ทั้งในหน่วยบล็อกและหน่วยวินาที จะมีค่าเกินหนึ่งปีเล็กน้อย
คำจำกัดความของ sequence encoding ตาม BIP68 (ที่มา: BIP68) แสดงผังไบนารีของค่า sequence ตามที่กำหนดใน BIP68

Outputs
ฟิลด์ outputs ของธุรกรรมประกอบด้วยฟิลด์หลายตัวที่เกี่ยวข้องกับเอาต์พุตแต่ละรายการ เช่นเดียวกับที่เราทำกับฟิลด์ inputs เราจะเริ่มจากการดูไบต์เฉพาะของฟิลด์ outputs จากตัวอย่างธุรกรรมที่ Alice จ่ายให้ Bob ซึ่งถูกแสดงในรูปแบบแผนที่ของไบต์เหล่านั้นในตัวอย่างตอนต้นของบทนี้

Output count (จำนวน Outputs)
เช่นเดียวกับจุดเริ่มต้นของส่วน inputs ของธุรกรรม ฟิลด์ outputs จะเริ่มต้นด้วยตัวนับที่ระบุจำนวนของ outputs ในธุรกรรมนี้ ซึ่งเป็น compactSize integer และต้องมีค่ามากกว่าศูนย์ โดยธุรกรรมตัวอย่างมีสอง outputs
Amount(จำนวนเงิน)
ฟิลด์แรกของ output หนึ่งรายการคือจำนวนเงินของมัน ซึ่งใน Bitcoin Core เรียกว่า “value” เป็น signed integer ขนาด 8 ไบต์ที่ระบุจำนวน satoshis ที่ต้องการโอน (satoshi คือหน่วยที่เล็กที่สุดของ bitcoin ที่สามารถแสดงได้ในธุรกรรมบนบล็อกเชน หนึ่ง bitcoin มี 100 ล้าน satoshis) และในกฎฉันทามติของ Bitcoin อนุญาตให้ output มีค่าตั้งแต่ศูนย์จนถึงสูงสุด 21 ล้าน bitcoins (2.1 quadrillion satoshis)
Uneconomical outputs and disallowed dust (เอาต์พุตที่ไม่คุ้มค่าเชิงเศรษฐกิจและฝุ่น (dust) ที่ถูกห้าม)
แม้ว่าจะไม่มีมูลค่า แต่เอาต์พุตที่มีค่าเป็นศูนย์สามารถถูกใช้จ่ายได้ภายใต้กฎเดียวกับเอาต์พุตอื่น ๆ อย่างไรก็ตาม การใช้จ่ายเอาต์พุต (นำมันไปใช้เป็นอินพุตในธุรกรรม) ทำให้ขนาดของธุรกรรมเพิ่มขึ้น ซึ่งทำให้ค่าธรรมเนียมที่ต้องจ่ายเพิ่มขึ้นด้วย หากมูลค่าของเอาต์พุตมีค่าน้อยกว่าค่าธรรมเนียมที่ต้องจ่ายเพิ่ม การใช้จ่ายเอาต์พุตนั้นก็ไม่มีความคุ้มค่าทางเศรษฐกิจ เอาต์พุตเช่นนี้เรียกว่า uneconomical outputs
เอาต์พุตที่มีค่าเป็นศูนย์เป็น uneconomical output เสมอ เพราะมันจะไม่เพิ่มมูลค่าใด ๆ ให้กับธุรกรรมที่ใช้มัน แม้ว่าอัตราค่าธรรมเนียมจะเป็นศูนย์ก็ตาม แต่อย่างไรก็ตาม เอาต์พุตอื่น ๆ ที่มีมูลค่าต่ำก็อาจเป็น uneconomical เช่นกัน แม้จะไม่ได้ตั้งใจ ตัวอย่างเช่น ที่อัตราค่าธรรมเนียมปกติบนเครือข่ายในวันนี้ เอาต์พุตหนึ่งอาจเพิ่มมูลค่ามากกว่าค่าใช้จ่ายในการนำไปใช้ แต่วันพรุ่งนี้อัตราค่าธรรมเนียมอาจสูงขึ้นและทำให้เอาต์พุตนั้นกลายเป็น uneconomical
ความจำเป็นที่ full node ต้องติดตาม UTXO ทั้งหมด ตามที่อธิบายไว้ในหัวข้อ Outpoint หมายความว่า UTXO ทุกตัวทำให้การรัน full node ยากขึ้นเล็กน้อย สำหรับ UTXO ที่มีมูลค่ามาก ผู้ใช้มีแรงจูงใจที่จะใช้จ่ายมันในที่สุด ดังนั้นมันไม่ก่อปัญหา แต่สำหรับผู้ที่ถือ UTXO ที่ไม่คุ้มค่าทางเศรษฐกิจ ไม่มีแรงจูงใจที่จะใช้จ่ายมันเลย ซึ่งอาจทำให้มันกลายเป็นภาระถาวรสำหรับผู้ดูแล full node และเนื่องจากการกระจายศูนย์ของ Bitcoin ขึ้นอยู่กับจำนวนผู้ที่ยินดีรัน full node ซอฟต์แวร์ full node หลาย ๆ ตัว เช่น Bitcoin Core จึงใช้ข้อกำหนดที่ส่งผลต่อการส่งต่อธุรกรรมและการขุดธุรกรรมที่ยังไม่ได้คอนเฟิร์ม เพื่อป้องกันการสร้างเอาต์พุตที่ไม่คุ้มค่าทางเศรษฐกิจ
ข้อกำหนดที่ห้ามการส่งต่อหรือการขุดธุรกรรมที่สร้างเอาต์พุตที่ไม่คุ้มค่าเรียกว่า dust policies ตั้งตามการเปรียบเทียบเชิงสัญลักษณ์ระหว่างเอาต์พุตที่มีมูลค่าน้อยมากกับอนุภาคที่มีขนาดเล็กมาก (เล็กจนเป็นฝุ่น) dust policy ของ Bitcoin Core นั้นซับซ้อนและมีตัวเลขกำหนดหลายค่า ดังนั้นโปรแกรมจำนวนมากมักถือเอาว่าเอาต์พุตที่มีค่าน้อยกว่า 546 satoshis คือ dust และจะไม่ถูกส่งต่อหรือขุดตามค่าตั้งต้น แต่ก็มีบ้างเป็นครั้งคราวที่มีข้อเสนอให้ลดขีดจำกัดของ dust และก็มีข้อเสนอคัดค้านให้เพิ่มขีดจำกัดเช่นกัน ดังนั้นเราขอแนะนำให้นักพัฒนาที่ใช้ presigned transactions หรือโปรโตคอลแบบหลายฝ่าย ตรวจสอบว่าข้อกำหนดนี้มีการเปลี่ยนแปลงตั้งแต่วันที่หนังสือเล่มนี้เผยแพร่หรือไม่
TIP: ตั้งแต่การกำเนิดของ Bitcoin full node ทุกตัวจำเป็นต้องเก็บสำเนาของ UTXO ทุกตัว แต่สิ่งนี้อาจไม่จำเป็นต้องเป็นเช่นนั้นตลอดไป นักพัฒนาหลายคนกำลังทำงานกับ Utreexo ซึ่งเป็นโปรเจกต์ที่ทำให้โหนดเต็มสามารถเก็บข้อมูลแบบ commitment ต่อชุดของ UTXO แทนการเก็บข้อมูลจริง commitment ที่เล็กที่สุดอาจมีขนาดเพียงหนึ่งหรือสองกิโลไบต์—เทียบกับข้อมูลมากกว่า 5 กิกะไบต์ที่ Bitcoin Core เก็บ ณ เวลาที่เขียนหนังสือนี้ (และตอนที่ผมแปลอยู่ที่ประมาณ 11 GB)
อย่างไรก็ตาม Utreexo จะยังคงต้องให้บางโหนดเก็บข้อมูล UTXO ทั้งหมด โดยเฉพาะโหนดที่ให้บริการนักขุดหรือการดำเนินงานอื่น ๆ ที่ต้องตรวจสอบบล็อกใหม่อย่างรวดเร็ว นั่นหมายความว่า uneconomical outputs ก็ยังคงเป็นปัญหาสำหรับ full node แม้ในอนาคตที่เป็นไปได้ซึ่งโหนดส่วนใหญ่ใช้ Utreexo ก็ตาม
กฎนโยบายของ Bitcoin Core เกี่ยวกับ dust มีข้อยกเว้นหนึ่งข้อ: output script ที่เริ่มต้นด้วย OP_RETURN ซึ่งเรียกว่า data carrier outputs สามารถมีมูลค่าเป็นศูนย์ได้ OP_RETURN opcode ทำให้สคริปต์ล้มเหลวทันทีไม่ว่าจะมีอะไรตามมาหลังจากนั้น ดังนั้นเอาต์พุตเหล่านี้ไม่สามารถถูกใช้จ่ายได้เลย นั่นหมายความว่า full node ไม่จำเป็นต้องติดตามเอาต์พุตเหล่านี้ ซึ่งเป็นคุณสมบัติที่ Bitcoin Core ใช้เพื่อให้ผู้ใช้สามารถจัดเก็บข้อมูลเล็กน้อยตามต้องการลงในบล็อกเชนได้โดยไม่ทำให้ฐานข้อมูล UTXO มีขนาดใหญ่ขึ้น เนื่องจากเอาต์พุตเหล่านี้ไม่สามารถใช้จ่ายได้ จึงไม่ถือว่าเป็น uneconomical แต่ว่า satoshis ใด ๆ ก็ตามที่กำหนดให้กับมันจะกลายเป็นใช้ไม่ได้อย่างถาวร ดังนั้นการอนุญาตให้จำนวนเงินเป็นศูนย์จึงช่วยให้มั่นใจว่า satoshis จะไม่ถูกทำลาย
Output Scripts
หลังจากจำนวนเงินของเอาต์พุต จะมี compactSize integer ที่บอกความยาวของ output script ซึ่งเป็นสคริปต์ที่มีเงื่อนไขที่ต้องปฏิบัติตามเพื่อใช้จ่ายบิตคอยน์ ตามกฎฉันทามติของ Bitcoin ขนาดขั้นต่ำของ output script คือศูนย์
ขนาดสูงสุดตามฉันทามติที่อนุญาตของ output script จะแตกต่างกันไปขึ้นอยู่กับช่วงเวลาที่มีการตรวจสอบ ไม่มีขีดจำกัดที่ระบุไว้อย่างชัดเจนสำหรับขนาดของ output script ในเอาต์พุตของธุรกรรม แต่ธุรกรรมถัดไปสามารถใช้จ่ายเอาต์พุตก่อนหน้าได้เฉพาะเมื่อสคริปต์มีขนาด 10,000 ไบต์หรือน้อยกว่าเท่านั้น โดยนัยคือสคริปต์ของเอาต์พุตสามารถมีขนาดใหญ่เกือบเท่ากับตัวธุรกรรมที่บรรจุมันไว้ และธุรกรรมก็สามารถมีขนาดใหญ่เกือบเท่ากับบล็อกที่บรรจุธุรกรรมนั้นไว้
TIP: output script ที่มีความยาวเป็นศูนย์สามารถถูกใช้จ่ายได้โดย input script ที่มี OP_TRUE อยู่ภายใน โดยใคร ๆ ก็สามารถสร้าง input script แบบนั้นได้ ซึ่งหมายความว่าใครก็สามารถใช้จ่าย output script ที่ว่างเปล่าได้ มีจำนวนสคริปต์แทบไม่จำกัดที่ใครก็สามารถใช้จ่ายได้ และนักพัฒนาโปรโตคอล Bitcoin เรียกสิ่งเหล่านี้ว่า anyone can spends การอัปเกรดภาษาสคริปต์ของ Bitcoin มักจะนำสคริปต์ที่เป็น anyone-can-spend มาเพิ่มเงื่อนไขใหม่เข้าไป ทำให้สามารถใช้จ่ายได้เฉพาะภายใต้เงื่อนไขใหม่เท่านั้น นักพัฒนาแอปพลิเคชันไม่ควรจำเป็นต้องใช้ anyone-can-spend script เลย แต่หากคุณจำเป็นต้องใช้ เราขอแนะนำอย่างยิ่งให้ประกาศแผนของคุณอย่างชัดเจนต่อผู้ใช้และนักพัฒนา Bitcoin เพื่อไม่ให้การอัปเกรดในอนาคตไปรบกวนระบบของคุณโดยไม่ตั้งใจ
นโยบายของ Bitcoin Core สำหรับการรีเลย์และการขุดธุรกรรม จะจำกัด output script ให้เหลือเพียงไม่กี่รูปแบบ ซึ่งเรียกว่า standard transaction outputs การจำกัดนี้ถูกนำมาใช้ครั้งแรกหลังจากค้นพบบั๊กในยุคแรกของ Bitcoin ที่เกี่ยวข้องกับภาษา Script และยังถูกคงไว้ใน Bitcoin Core ยุคปัจจุบันเพื่อรองรับการอัปเกรดแบบ anyone-can-spend และเพื่อส่งเสริมแนวทางที่ดีที่สุดในการวางเงื่อนไขสคริปต์ไว้ใน P2SH redeem scripts, segwit v0 witness scripts และ segwit v1 (taproot) leaf scripts เราจะดูแต่ละรูปแบบของ standard transaction templates ในปัจจุบัน และเรียนรู้วิธีการ parse สคริปต์ในบทถัดไป
Witness Structure
ในศาล “พยาน” คือบุคคลที่ให้การว่าตนเห็นเหตุการณ์สำคัญบางอย่างเกิดขึ้น พยานมนุษย์ไม่ใช่สิ่งที่เชื่อถือได้เสมอไป ดังนั้นศาลจึงมีขั้นตอนต่าง ๆ สำหรับการสอบสวนพยานเพื่อ (อย่างน้อยก็ในอุดมคติ) รับหลักฐานเฉพาะจากผู้ที่เชื่อถือได้เท่านั้น
ลองจินตนาการว่าถ้ามี “พยาน” สำหรับโจทย์คณิตศาสตร์จะมีหน้าตาอย่างไร ตัวอย่างเช่น หากปัญหาสำคัญคือ x + 2 == 4 และมีใครสักคนอ้างว่าตนเป็นพยานที่เห็นวิธีแก้ เราจะถามอะไรพวกเขา? เราต้องการ “หลักฐานทางคณิตศาสตร์” ที่แสดงค่าหนึ่งที่สามารถนำมาบวกกับ 2 แล้วได้ 4 เราอาจตัดความจำเป็นของการมีบุคคลออกไปเลย และใช้ “ค่าที่เสนอให้เป็น x” นั้นเป็นพยานแทนก็ได้ หากเราถูกบอกว่าพยานคือ 2 เราก็สามารถแทนค่าในสมการ ตรวจสอบว่าถูกต้อง และสรุปว่าปัญหาสำคัญนั้นได้รับการแก้ไขแล้ว
เมื่อมีการใช้จ่ายบิตคอยน์ ปัญหาสำคัญที่เราต้องการแก้คือ “การยืนยันว่าการใช้จ่ายนั้นได้รับอนุญาตจากบุคคลหรือกลุ่มบุคคลที่ควบคุมบิตคอยน์เหล่านั้นจริงหรือไม่” โหนดเต็มนับพันที่บังคับใช้กฎฉันทามติของบิตคอยน์ไม่สามารถสอบสวนพยานมนุษย์ได้ แต่พวกมันสามารถรับ “พยาน” ที่ประกอบด้วยข้อมูลล้วน ๆ สำหรับแก้ปัญหาทางคณิตศาสตร์ได้ ตัวอย่างเช่น พยานที่เป็นค่า 2 จะทำให้สามารถใช้จ่ายบิตคอยน์ที่ถูกป้องกันด้วยสคริปต์ต่อไปนี้ได้:
2 OP_ADD 4 OP_EQUAL
แน่นอนว่า การอนุญาตให้บิตคอยน์ของคุณถูกใช้จ่ายโดยใครก็ตามที่สามารถแก้สมการง่าย ๆ ได้ย่อมไม่ปลอดภัย อย่างที่เราจะเห็นในบทที่ 8 กลไกการลงนามดิจิทัลที่ปลอมแปลงไม่ได้ (unforgeable digital signature scheme) ใช้สมการที่สามารถถูกแก้ได้โดยเฉพาะผู้ที่ครอบครองข้อมูลบางอย่างซึ่งพวกเขาสามารถเก็บไว้เป็นความลับได้ พวกเขาสามารถอ้างอิงข้อมูลลับนั้นโดยใช้ตัวระบุสาธารณะ ซึ่งตัวระบุดังกล่าวเรียกว่า public key และคำตอบของสมการนั้นเรียกว่า signature
สคริปต์ต่อไปนี้มี public key และ opcode ที่ต้องการ signature ที่สอดคล้องกันเพื่อ commit กับข้อมูลในธุรกรรมที่กำลังใช้จ่าย เช่นเดียวกับตัวเลข 2 ในตัวอย่างง่าย ๆ ของเรา signature คือพยานของเรา:
<public key> OP_CHECKSIG
พยาน (witnesses) ซึ่งเป็นค่าที่ใช้ในการแก้ปัญหาทางคณิตศาสตร์ที่ปกป้องบิตคอยน์ จำเป็นต้องถูกใส่รวมอยู่ในธุรกรรมที่ใช้มัน เพื่อให้โหนดเต็มสามารถตรวจสอบได้ ในรูปแบบธุรกรรมแบบดั้งเดิม (legacy transaction format) ที่ใช้ในธุรกรรมบิตคอยน์ยุคแรก ๆ ทั้งหมดนั้น ลายเซ็นและข้อมูลอื่น ๆ ถูกวางไว้ในฟิลด์ input script อย่างไรก็ตาม เมื่อผู้พัฒนาเริ่มนำโปรโตคอลสัญญาต่าง ๆ มาใช้บนบิตคอยน์ เช่นที่เราเห็นใน Original sequence-based transaction replacement พวกเขาค้นพบปัญหาที่สำคัญหลายประการเกี่ยวกับการใส่พยานไว้ในฟิลด์ input script
Circular Dependencies
โปรโตคอลสัญญาหลายอย่างสำหรับบิตคอยน์เกี่ยวข้องกับชุดของธุรกรรมที่ถูกเซ็นนอกลำดับ ตัวอย่างเช่น Alice และ Bob ต้องการฝากเงินเข้าไปในสคริปต์ที่สามารถใช้จ่ายได้ด้วยลายเซ็นของทั้งคู่ แต่พวกเขาก็ต้องการให้สามารถได้เงินคืนได้หากอีกฝ่ายหนึ่งไม่ตอบสนอง วิธีแก้ปัญหาอย่างง่ายคือการเซ็นธุรกรรมนอกลำดับ:
- Tx0 จ่ายเงินจาก Alice และเงินจาก Bob ไปยังเอาต์พุตที่มีสคริปต์ที่ต้องการลายเซ็นจากทั้ง Alice และ Bob เพื่อที่จะใช้จ่ายได้
- Tx1 ใช้จ่ายเอาต์พุตก่อนหน้าไปยังสองเอาต์พุต—หนึ่งคืนเงินให้ Alice และอีกหนึ่งคืนเงินให้ Bob (หักจำนวนเล็กน้อยเป็นค่าธรรมเนียมธุรกรรม)
- หาก Alice และ Bob เซ็น Tx1 ก่อนที่พวกเขาจะเซ็น Tx0 พวกเขาทั้งคู่ก็จะมั่นใจได้ว่าจะรับเงินคืนได้ทุกเมื่อ โปรโตคอลนี้ไม่ต้องการให้ฝ่ายหนึ่งไว้ใจอีกฝ่ายหนึ่ง ทำให้มันเป็นโปรโตคอลแบบ trustless
ปัญหาของโครงสร้างนี้ในรูปแบบธุรกรรมแบบดั้งเดิมคือทุกฟิลด์ รวมถึงฟิลด์ input script ที่มีลายเซ็น จะถูกใช้ในการสร้างตัวระบุของธุรกรรม (txid) ค่า txid ของ Tx0 เป็นส่วนหนึ่งของ outpoint ของอินพุตใน Tx1 นั่นหมายความว่าไม่มีทางที่ Alice และ Bob จะสร้าง Tx1 ได้จนกว่าจะทราบลายเซ็นทั้งสองของ Tx0 — แต่ถ้าพวกเขารู้ลายเซ็นของ Tx0 หนึ่งในนั้นสามารถออกอากาศธุรกรรมนั้นก่อนที่จะเซ็นธุรกรรมคืนเงิน ทำให้การรับประกันการคืนเงินถูกลบล้างไป นี่คือปัญหา circular dependency
Third-Party Transaction Malleability (การเปลี่ยนแปลงธุรกรรมโดยบุคคลที่สาม)
ลำดับธุรกรรมที่ซับซ้อนกว่าสามารถแก้ปัญหา circular dependency ได้ในบางครั้ง แต่โปรโตคอลจำนวนมากจะพบกับความกังวลใหม่: มักจะสามารถแก้สคริปต์เดียวกันได้หลายวิธี ตัวอย่างเช่น พิจารณาสคริปต์แบบง่ายของเราจากหัวข้อ Witness Structure:
2 OP_ADD 4 OP_EQUAL
เราสามารถทำให้สคริปต์นี้ผ่านได้โดยการใส่ค่า 2 ลงใน input script แต่มีหลายวิธีที่จะวางค่านั้นลงบนสแตกในบิตคอยน์ ต่อไปนี้คือเพียงบางตัวอย่าง:
OP_2
OP_PUSH1 0x02
OP_PUSH2 0x0002
OP_PUSH3 0x000002
...
OP_PUSHDATA1 0x0102
OP_PUSHDATA1 0x020002
...
OP_PUSHDATA2 0x000102
OP_PUSHDATA2 0x00020002
...
OP_PUSHDATA4 0x0000000102
OP_PUSHDATA4 0x000000020002
...
แต่ละรูปแบบการเข้ารหัสของตัวเลข 2 ใน input script จะสร้างธุรกรรมที่แตกต่างกันเล็กน้อย พร้อมกับ txid ที่แตกต่างกันอย่างสิ้นเชิง แต่ละเวอร์ชันของธุรกรรมจะใช้จ่ายอินพุต (outpoints) เดียวกันกับทุกเวอร์ชันอื่น ๆ ทำให้พวกมันขัดแย้งกันเอง ทั้งหมดนี้ทำให้มีเพียงหนึ่งเวอร์ชันจากชุดของธุรกรรมที่ขัดแย้งกันเท่านั้นที่สามารถอยู่ในบล็อกเชนที่ถูกต้องได้
ลองนึกภาพว่ามีครั้งหนึ่ง Alice สร้างเวอร์ชันของธุรกรรมที่มี OP_2 ใน input script และมี output ที่จ่ายให้ Bob จากนั้น Bob ก็ใช้จ่าย output นั้นต่อให้ Carol ทันที บุคคลใดก็ตามบนเครือข่ายสามารถแทนที่ OP_2 ด้วย OP_PUSH1 0x02 ทำให้เกิดธุรกรรมที่ขัดแย้งกับเวอร์ชันต้นฉบับของ Alice หากธุรกรรมที่ขัดแย้งนั้นถูกยืนยัน ก็จะไม่มีทางรวมเวอร์ชันต้นฉบับของ Alice เข้าไปในบล็อกเชนเดียวกันได้ ซึ่งหมายความว่าไม่มีทางที่ธุรกรรมของ Bob จะใช้จ่าย output ของมันได้ การจ่ายเงินของ Bob ให้ Carol จึงกลายเป็นโมฆะ ทั้งที่ Alice, Bob หรือ Carol ไม่ได้ทำอะไรผิดเลย คนที่ไม่ได้เกี่ยวข้องกับธุรกรรม (บุคคลที่สาม) สามารถเปลี่ยนแปลง (mutate) ธุรกรรมของ Alice ได้ ซึ่งเป็นปัญหาที่เรียกว่า unwanted third-party transaction malleability
TIP: มีหลายกรณีที่ผู้คนต้องการให้ธุรกรรมของพวกเขาถูกดัดแปลงได้ (malleable) และบิตคอยน์ก็มีฟีเจอร์หลายอย่างรองรับสิ่งนี้ โดยเฉพาะ signature hashes (sighash) ที่เราจะได้เรียนรู้ใน [sighash_types] ตัวอย่างเช่น Alice สามารถใช้ sighash เพื่อให้ Bob ช่วยจ่ายค่าธรรมเนียมบางส่วนได้ ซึ่งทำให้ธุรกรรมของ Alice ถูกเปลี่ยนแปลง แต่เป็นการเปลี่ยนแปลงในแบบที่ Alice ต้องการ ด้วยเหตุนี้เราจะใส่คำว่า unwanted นำหน้าคำว่า transaction malleability ในบางครั้ง แม้ว่าเราหรือผู้เขียนเทคนิคบิตคอยน์คนอื่น ๆ จะใช้คำเวอร์ชันสั้น เราก็กำลังพูดถึงรูปแบบของ malleability ที่ไม่พึงประสงค์แทบทุกครั้ง
Second-Party Transaction Malleability(ผู้ร่วมธุรกรรมอีกฝ่าย)
เมื่อรูปแบบธุรกรรมแบบดั้งเดิม (legacy transaction format) เป็นรูปแบบเดียวที่มีให้ใช้ นักพัฒนาก็ทำงานบนข้อเสนอเพื่อลดปัญหา third-party malleability เอาไว้อย่างเช่น BIP62 แต่อย่างไรก็ตาม แม้ว่าจะสามารถกำจัด third-party malleability ได้ทั้งหมด ผู้ใช้งานโปรโตคอลแบบสัญญา (contract protocols) ก็ยังพบปัญหาอีกแบบหนึ่ง คือถ้าต้องใช้ลายเซ็นของอีกฝ่ายหนึ่งที่อยู่ในโปรโตคอล ฝ่ายนั้นสามารถสร้างลายเซ็นทางเลือกขึ้นมาและเปลี่ยน txid ได้
ตัวอย่างเช่น Alice และ Bob ฝากเงินของพวกเขาเข้าไปในสคริปต์ที่ต้องการลายเซ็นของทั้งคู่เพื่อใช้จ่าย พวกเขายังสร้าง "ธุรกรรมคืนเงิน" (refund transaction) ที่จะให้ทั้งสองฝ่ายสามารถดึงเงินกลับได้ทุกเมื่อ และต่อมา Alice ต้องการใช้เงินบางส่วน เธอจึงร่วมมือกับ Bob สร้างลำดับธุรกรรมดังนี้:
- Tx0 รวมลายเซ็นจาก Alice และ Bob และใช้จ่ายบิตคอยน์ไปยังสอง output โดย output แรกจ่ายเงินบางส่วนให้ Alice และอีกอัน คืนเงินส่วนที่เหลือกลับเข้าไปยังสคริปต์เดิม ซึ่งยังคงต้องใช้ลายเซ็นของทั้ง Alice และ Bob แน่นอนว่าก่อนที่พวกเขาจะเซ็น Tx0 พวกเขาสร้างธุรกรรมคืนเงินชุดใหม่ Tx1
- Tx1 ใช้จ่าย output ที่สองของ Tx0 ไปยังสอง output ใหม่—หนึ่งให้ Alice ตามส่วนแบ่งของเธอ และอีกหนึ่งให้ Bob ตามส่วนแบ่งของเขา Alice และ Bob ต่างเซ็น Tx1 เสร็จก่อนที่จะเซ็น Tx0
ตรงนี้ไม่มี circular dependency และถ้าเรามองข้าม third-party malleability ก็จะดูเหมือนเป็นโปรโตคอลที่ไม่ต้องเชื่อใจใคร (trustless) ได้ แต่ปัญหาคือ คุณสมบัติของลายเซ็นบิตคอยน์มีความสุ่มโดยธรรมชาติ —ผู้เซ็นต้องเลือกตัวเลขสุ่มขนาดใหญ่ทุกครั้งที่สร้างลายเซ็น หากเลือกเลขสุ่มต่างกัน ลายเซ็นที่ได้ก็จะต่างกัน แม้ข้อมูลที่ถูกเซ็นจะเหมือนเดิมทั้งหมดก็ตาม คล้ายกับการที่คุณเซ็นชื่อบนสัญญาเหมือนกันสองชุด ลายเซ็นจริง ๆ บนกระดาษทั้งสองแผ่นก็ไม่เหมือนกัน 100%
ความสามารถในการถูกดัดแปลงของลายเซ็นนี้ เป็นช่องให้ Bob สร้างธุรกรรมที่ขัดแย้งกับ Tx0 ได้ หาก Alice พยายามประกาศ Tx0 (ซึ่งมีลายเซ็นของ Bob อยู่) Bob สามารถสร้างลายเซ็นใหม่ที่แตกต่างออกมา ผลิต Tx0 เวอร์ชันใหม่ที่มี txid แตกต่างกันขึ้นมาได้ ถ้า Tx0 เวอร์ชันที่ Bob เปลี่ยนแปลงถูกยืนยันขึ้นมา Alice จะไม่สามารถใช้ Tx1 ที่ลงนามไว้ล่วงหน้าเพื่อรับเงินคืนของเธอได้ เพราะ Tx1 อ้างอิง txid เดิมของ Tx0 ซึ่งไม่ตรงกับเวอร์ชันที่ถูกยืนยัน พฤติกรรมแบบนี้เรียกว่า unwanted second-party transaction malleability คือการที่อีกฝ่ายในโปรโตคอลสามารถดัดแปลงธุรกรรมให้เปลี่ยน txid ได้โดยไม่ต้องร่วมมือกับคุณ
Segregated Witness(การแยกพยานออกจากธุรกรรม)
ตั้งแต่ช่วงต้นปี 2011 นักพัฒนาระบบโปรโตคอลของบิตคอยน์ก็ทราบวิธีแก้ปัญหา circular dependence, third-party malleability, และ second-party malleability แล้ว แนวคิดคือการหลีกเลี่ยงไม่ให้นำ input script มารวมอยู่ในการคำนวณที่ใช้สร้าง txid ของธุรกรรม จำได้ว่า ชื่อเชิงนามธรรมสำหรับข้อมูลที่อยู่ใน input script คือคำว่า witness แนวคิดในการแยกข้อมูลส่วนอื่น ๆ ของธุรกรรมออกจาก witness เพื่อใช้ในการสร้าง txid นี้ เรียกว่า segregated witness (segwit)
วิธีที่ตรงไปตรงมาที่สุดในการทำให้ segwit ใช้งานได้จริงนั้น ต้องมีการเปลี่ยนแปลงกฎ consensus ของบิตคอยน์ในลักษณะที่ ไม่สามารถเข้ากันได้กับ full node รุ่นเก่า หรือที่เรียกว่า hard fork Hard fork นั้นมาพร้อมกับความท้าทายจำนวนมาก ซึ่งเราจะอธิบายเพิ่มเติมในส่วน hardfork
แนวทางทางเลือกสำหรับ segwit ถูกอธิบายไว้ในช่วงปลายปี 2015 แนวทางนี้ใช้การเปลี่ยนแปลงกฎ consensus แบบ เข้ากันได้ย้อนหลัง (backward-compatible) ซึ่งเรียกว่า soft fork คำว่า “เข้ากันได้ย้อนหลัง” หมายความว่า full node ที่ใช้กฎใหม่จะต้อง ไม่ยอมรับบล็อกใด ๆ ที่ full node รุ่นเก่ามองว่าไม่ถูกต้อง ตราบเท่าที่พวกเขาปฏิบัติตามกฎนั้น full node รุ่นใหม่จึงสามารถ ปฏิเสธบล็อกที่ full node รุ่นเก่ายอมรับได้ ซึ่งทำให้พวกเขาสามารถบังคับใช้กฎ consensus ใหม่ได้ (แต่มีเงื่อนไขว่า full node รุ่นใหม่เหล่านั้นจะต้อง เป็นตัวแทนของฉันทามติทางเศรษฐกิจของผู้ใช้บิตคอยน์ด้วย—เราจะสำรวจรายละเอียดเกี่ยวกับการอัปเกรดกฎ consensus ของบิตคอยน์ในส่วนของ mining
การใช้ซอฟต์ฟอร์กแบบเซกวิทอาศัยแนวคิดเอาต์พุตสคริปต์แบบ “anyone-can-spend” เป็นพื้นฐาน สคริปต์ที่ขึ้นต้นด้วยตัวเลขตั้งแต่ 0 ถึง 16 และตามด้วยข้อมูลขนาด 2 ถึง 40 ไบต์ ถูกกำหนดให้เป็นแม่แบบเอาต์พุตสคริปต์ของเซกวิท ตัวเลขนั้นคือหมายเลขเวอร์ชันของเซกวิท (เช่น 0 คือเซกวิทเวอร์ชัน 0 หรือ segwit v0) ส่วนข้อมูลที่ตามมานั้นเรียกว่า witness program นอกจากนี้ยังสามารถนำแม่แบบเซกวิทไปห่อด้วยคอมมิตเมนต์แบบ P2SH ได้ แต่ในบทนี้เราจะยังไม่กล่าวถึง
จากมุมมองของโหนดเก่า เอาต์พุตสคริปต์แม่แบบเหล่านี้สามารถถูกใช้จ่ายด้วย input script ที่ว่างเปล่าได้ จากมุมมองของโหนดใหม่ที่รู้กฎของเซกวิท การใช้จ่ายเอาต์พุตที่เป็นแม่แบบเซกวิทต้องใช้ input script ที่ว่างเปล่าเท่านั้น สังเกตความแตกต่าง: โหนดเก่า “อนุญาต” ให้ใช้สคริปต์ว่างเปล่า ส่วนโหนดใหม่ “กำหนด” ให้ต้องเป็นสคริปต์ว่างเปล่า
การใช้ input script ว่างเปล่าทำให้ข้อมูลพยานไม่ไปกระทบกับ txid ช่วยกำจัดปัญหาการพึ่งพาวนซ้ำ การเปลี่ยนแปลงธุรกรรมโดยบุคคลที่สาม และการเปลี่ยนแปลงธุรกรรมโดยคู่สัญญา แต่เมื่อไม่สามารถใส่ข้อมูลใด ๆ ใน input script ได้ ผู้ใช้แม่แบบเอาต์พุตเซกวิทจึงต้องมีฟิลด์ใหม่ ฟิลด์นี้คือโครงสร้างพยาน (witness structure)
การเกิดขึ้นของ witness programs และ witness structure ทำให้บิตคอยน์ซับซ้อนขึ้น แต่สิ่งนี้ก็สอดคล้องกับแนวโน้มที่มีอยู่แล้วของการเพิ่มระดับนามธรรมในระบบบิตคอยน์ จากที่กล่าวไว้ในบทที่ 4 ว่าไวท์เปเปอร์บิตคอยน์ดั้งเดิมอธิบายระบบที่บิตคอยน์ถูก “รับ” ไปยัง public keys (pubkeys) และถูก “ใช้จ่าย” ด้วย signatures (sigs) pubkey ระบุว่าใครได้รับอนุญาตให้ใช้จ่ายบิตคอยน์ (คือผู้ที่ควบคุม private key ที่สอดคล้องกัน) และลายเซ็นเป็นการตรวจสอบว่าธุรกรรมการใช้จ่ายนั้นมาจากผู้ที่ควบคุม private key จริง ๆ เพื่อทำให้ระบบนั้นยืดหยุ่นขึ้น รุ่นแรกสุดของบิตคอยน์ได้แนะนำ scripts ซึ่งอนุญาตให้รับบิตคอยน์ไปยัง output scripts และใช้จ่ายด้วย input scripts ประสบการณ์ภายหลังจากการใช้งานโปรโตคอลสัญญาต่าง ๆ ได้นำไปสู่การอนุญาตให้รับบิตคอยน์ไปยัง witness programs และใช้จ่ายด้วย witness structure คำต่าง ๆ และฟิลด์ที่ถูกใช้ในเวอร์ชันต่าง ๆ ของบิตคอยน์ถูกแสดงไว้ในตารางข้างล่างนี้
| | Authorization | Authentication | | ----- | ----- | ----- | | Whitepaper | Public key | Signature | | Original (Legacy) | Output script | Input script | | Segwit | Witness program | Witness structure |
Witness Structure Serialization
เช่นเดียวกับฟิลด์ inputs และ outputs, witness structure ก็มีฟิลด์ย่อยอื่น ๆ อยู่ภายใน ดังนั้นเราจะเริ่มต้นด้วยแผนที่ไบต์ (byte map) ของข้อมูลเหล่านั้นจากธุรกรรมของ Alice ซึ่งแสดงอยู่ในรูปด้านล่าง

ต่างจากฟิลด์ inputs และ outputs ตรงที่ witness structure โดยรวมจะไม่เริ่มต้นด้วยตัวบ่งชี้จำนวนของ witness stack ทั้งหมดที่มีอยู่ แต่จำนวนดังกล่าวถูกกำหนดโดยฟิลด์ inputs—กล่าวคือ ธุรกรรมหนึ่งรายการจะมีหนึ่ง witness stack ต่อหนึ่ง input เสมอ
สำหรับ witness structure ของแต่ละ input จะเริ่มต้นด้วยตัวนับจำนวนขององค์ประกอบที่อยู่ภายใน ซึ่งองค์ประกอบเหล่านี้เรียกว่า witness items เราจะศึกษารายละเอียดของมันในภายหลังในบทต่อไป แต่ตอนนี้สิ่งที่ต้องรู้คือ witness item แต่ละรายการจะถูกนำหน้าด้วย compactSize integer ที่ระบุขนาดของมัน
สำหรับ legacy inputs จะไม่มี witness items อยู่เลย ดังนั้น witness stack ของมันประกอบด้วยค่า 0 เพียงค่าเดียว (0x00)
ธุรกรรมของ Alice มี input หนึ่งรายการ และมี witness item หนึ่งรายการ
Lock Time
ฟิลด์สุดท้ายในธุรกรรมที่ถูกซีเรียลไลซ์คือ lock time ฟิลด์นี้เป็นส่วนหนึ่งของรูปแบบการซีเรียลไลซ์ดั้งเดิมของบิตคอยน์ แต่ในช่วงแรกมีผลเฉพาะกับนโยบายการเลือกธุรกรรมเข้าสู่บล็อกของบิตคอยน์เท่านั้น
ซอฟต์ฟอร์กที่บันทึกได้เป็นครั้งแรกของบิตคอยน์ได้เพิ่มกฎใหม่ว่า เริ่มตั้งแต่บล็อกหมายเลข 31,000 เป็นต้นไป ห้ามรวมธุรกรรมในบล็อก เว้นแต่ธุรกรรมนั้นจะเป็นไปตามหนึ่งในกฎต่อไปนี้:
- ธุรกรรมระบุว่า สามารถถูกรวมในบล็อกใดก็ได้ โดยตั้งค่า lock time เป็น 0
- ธุรกรรมระบุว่าต้องการจำกัดว่าบล็อกใดที่สามารถรวมมันได้ โดยตั้งค่า lock time ให้มีค่าต่ำกว่า 500,000,000 ในกรณีนี้ ธุรกรรมสามารถถูกบรรจุได้เฉพาะในบล็อกที่มีความสูงของบล็อก (block height) เท่ากับ lock time หรือมากกว่า ตัวอย่างเช่น ธุรกรรมที่มี lock time เป็น 123,456 สามารถอยู่ในบล็อก 123,456 หรือบล็อกใดที่สูงกว่า
- ธุรกรรมระบุว่าต้องการจำกัดเวลาที่สามารถถูกบรรจุเข้าสู่บล็อกเชนได้ โดยตั้งค่า lock time ให้มีค่า 500,000,000 หรือมากกว่า ในกรณีนี้ lock time จะถูกตีความเป็นเวลาตาม epoch time (จำนวนวินาทีตั้งแต่ 1970-01-01T00:00 UTC) ธุรกรรมจะถูกบรรจุได้เฉพาะในบล็อกที่มี median time past (MTP) มากกว่า lock time โดยทั่วไป MTP จะช้ากว่าเวลาปัจจุบันประมาณหนึ่งถึงสองชั่วโมง กฎของ MTP อธิบายไว้ในส่วนของ mtp
Coinbase Transactions
ธุรกรรมแรกในทุกบล็อกเป็นกรณีพิเศษ เอกสารรุ่นเก่าหลายฉบับเรียกธุรกรรมนี้ว่า generation transaction แต่เอกสารรุ่นใหม่ส่วนใหญ่เรียกว่า coinbase transaction (ไม่เกี่ยวข้องกับธุรกรรมที่สร้างโดยบริษัทชื่อ “Coinbase”)
Coinbase transactions ถูกสร้างขึ้นโดยนักขุดที่ขุดบล็อกนั้น และเปิดโอกาสให้นักขุดสามารถรับ ค่าธรรมเนียม (fees) ทั้งหมดจากธุรกรรมที่อยู่ในบล็อกนั้นได้ นอกจากนี้ จนถึงบล็อกหมายเลข 6,720,000 นักขุดยังสามารถรับ subsidy ซึ่งเป็นบิตคอยน์ที่ถูกสร้างขึ้นใหม่และไม่เคยหมุนเวียนมาก่อน เรียกว่า block subsidy จำนวนรวมที่นักขุดสามารถรับได้จากบล็อกหนึ่ง ๆ — ประกอบด้วยค่าธรรมเนียมและ block subsidy — เรียกว่า block reward
พฤติกรรมพิเศษบางประการของ coinbase transactions มีดังนี้:
- ธุรกรรม coinbase สามารถมีอินพุตได้เพียงหนึ่งรายการเท่านั้น
- อินพุตเพียงรายการนั้นต้องมี outpoint ที่มี txid เป็นค่าว่าง (ศูนย์ทั้งหมด) และมี output index สูงสุด (0xffffffff) สิ่งนี้ทำให้ coinbase transaction ไม่สามารถอ้างอิงเอาต์พุตของธุรกรรมก่อนหน้า ซึ่งจะสร้างความสับสนเพราะ coinbase transaction มีหน้าที่จ่ายค่าธรรมเนียมและ subsidy
- ฟิลด์ที่โดยปกติจะเป็น input script จะถูกเรียกว่า coinbase ในธุรกรรมประเภทนี้ และนี่คือที่มาของชื่อ coinbase transaction ฟิลด์ coinbase ต้องมีความยาวอย่างน้อย 2 ไบต์ และต้องไม่เกิน 100 ไบต์ สคริปต์นี้ ไม่ถูกประมวลผล แต่ยังคงอยู่ภายใต้กฎจำกัดจำนวน sigops แบบ legacy ดังนั้นข้อมูลใด ๆ ที่ใส่เข้าไปควรถูกนำหน้าด้วย opcode สำหรับการ push data นอกจากนี้ ตั้งแต่ soft fork ปี 2013 (BIP34) ไบต์แรก ๆ ของฟิลด์นี้ต้องปฏิบัติตามกฎเพิ่มเติม ซึ่งจะอธิบายทีหลัง
- ผลรวมของค่าเอาต์พุตทั้งหมดต้องไม่เกินค่าธรรมเนียมจากธุรกรรมทั้งหมดในบล็อกนั้นรวมกับ subsidy โดย subsidy เริ่มต้นที่ 50 BTC ต่อบล็อก และลดลงครึ่งหนึ่งทุก 210,000 บล็อก (ประมาณทุก 4 ปี) โดยปัดเศษลงถึง satoshi ที่ใกล้ที่สุด
- ตั้งแต่ soft fork segwit ปี 2017 (BIP141) บล็อกใดก็ตามที่มีธุรกรรมใช้จ่าย segwit output ต้องมีเอาต์พุตใน coinbase transaction ที่เป็นการ commit ถึงธุรกรรมทั้งหมดในบล็อก (รวมถึง witness ด้วย) รายละเอียดจะอธิบายในส่วน mining
coinbase transaction สามารถมีเอาต์พุตอื่น ๆ ที่ถูกต้องตามกฎเหมือนธุรกรรมทั่วไปได้ อย่างไรก็ตาม ธุรกรรมที่ใช้จ่ายเอาต์พุตของ coinbase จะไม่สามารถใส่ในบล็อกได้จนกว่าธุรกรรมนั้นจะมีอย่างน้อย 100 confirmations สิ่งนี้เรียกว่า maturity rule และเอาต์พุตของ coinbase ที่ยังไม่ถึง 100 confirmations จะถูกเรียกว่า immature แต่เนื่องจากลักษณะพิเศษของมัน ทำให้บางครั้งอาจเป็นสาเหตุของปัญหาที่ไม่คาดคิดในซอฟต์แวร์ที่ไม่ได้ออกแบบมาเพื่อรองรับกรณีเหล่านี้
Weight and Vbytes
แต่ละบล็อกของบิตคอยน์มีข้อจำกัดปริมาณข้อมูลธุรกรรมที่บรรจุได้ ดังนั้นซอฟต์แวร์บิตคอยน์ส่วนใหญ่จึงต้องสามารถวัดขนาดของธุรกรรมที่สร้างหรือประมวลผลได้ หน่วยวัดสมัยใหม่ที่ใช้คือ weight อีกหน่วยหนึ่งคือ vbytes ซึ่งคำนวณจากการนำ weight มาหารด้วยสี่ ทำให้สามารถเทียบกับหน่วยไบต์ในบล็อกแบบดั้งเดิมได้ง่ายขึ้น
ขนาดสูงสุดของบล็อกคือ 4 ล้าน weight ส่วน block header ใช้พื้นที่ไป 240 weight และช่องข้อมูล transaction count ใช้อีก 4 หรือ 12 weight พื้นที่ที่เหลือทั้งหมดสามารถใช้สำหรับข้อมูลธุรกรรมได้
การคำนวณ weight ของฟิลด์หนึ่ง ๆ ในธุรกรรมทำได้โดยการนำจำนวนไบต์ของฟิลด์ที่ serialize แล้วไปคูณกับตัวคูณเฉพาะฟิลด์นั้น ๆ จากนั้นเมื่อต้องการหาน้ำหนักรวมของธุรกรรม ก็เพียงนำ weight ของทุกฟิลด์มาบวกกัน ตัวคูณสำหรับแต่ละฟิลด์ถูกระบุไว้ในตารางข้างล่างนี้ นอกจากนี้ยังมีตัวอย่างการคำนวณ weight ของแต่ละฟิลด์ในธุรกรรมตัวอย่างของบทนี้ (ธุรกรรมจาก Alice ไปยัง Bob)
ตัวคูณเหล่านี้ รวมถึงฟิลด์ที่เกี่ยวข้อง ถูกออกแบบมาเพื่อลดน้ำหนักเมื่อมีการใช้ UTXO ช่วยลดแรงจูงใจในการสร้างเอาต์พุตที่ไม่คุ้มค่า (uneconomical outputs) และหลีกเลี่ยงปัญหา dust ที่ไม่เหมาะสมตามที่อธิบายไว้ในหัวข้อ Uneconomical outputs and disallowed dust
| Field | Factor | Weight in Alice’s Tx | | ----- | ----- | ----- | | Version | 4 | 16 | | Marker & Flag | 1 | 2 | | Inputs Count | 4 | 4 | | Outpoint | 4 | 144 | | Input script | 4 | 4 | | Sequence | 4 | 16 | | Outputs Count | 4 | 4 | | Amount | 4 | 64 (2 outputs) | | Output script | 4 | 232 (2 outputs with different scripts) | | Witness Count | 1 | 1 | | Witness items | 1 | 66 | | Lock time | 4 | 16 | | Total | N/A | 569 |
เราสามารถตรวจสอบความถูกต้องของการคำนวณ weight ได้โดยดูค่าน้ำหนักรวมของธุรกรรมของ Alice จาก Bitcoin Core ได้โดยตรง:
$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\\
38ecdfdfaa2a8029c1f9bcecd1f96177 2 | jq .weight
569

Legacy Serialization
รูปแบบการซีเรียลไลซ์ที่อธิบายไว้ในบทนี้คือรูปแบบที่ใช้กับธุรกรรมบิตคอยน์ส่วนใหญ่ในปัจจุบัน แต่ก็ยังมีรูปแบบเก่าที่ถูกใช้งานอยู่ในธุรกรรมจำนวนมากเช่นกัน รูปแบบเก่านั้นเรียกว่า legacy serialization และต้องใช้บนเครือข่าย Bitcoin P2P สำหรับธุรกรรมที่มีโครงสร้าง witness ว่างเปล่า (ซึ่งจะเกิดขึ้นได้ก็ต่อเมื่อธุรกรรมนั้นไม่ได้ใช้จ่าย witness programs ใด ๆ)
Legacy serialization จะ ไม่มี ฟิลด์ต่อไปนี้: marker, flag, witness structure
ในบทนี้ เราได้ดูฟิลด์ต่าง ๆ ในหนึ่งธุรกรรมและเห็นว่าฟิลด์เหล่านั้นสื่อสารข้อมูลให้โหนดเต็มทราบว่าเหรียญควรถูกโอนจากใครไปหาใครอย่างไร เราได้กล่าวถึง output script, input script และ witness structure เพียงคร่าว ๆ ซึ่งมีหน้าที่กำหนดเงื่อนไขและเปิดโอกาสให้ผู้ใช้พิสูจน์สิทธิ์ในการใช้จ่ายเหรียญได้
การเข้าใจวิธีสร้างและใช้งานเงื่อนไขเหล่านี้เป็นสิ่งสำคัญเพื่อให้แน่ใจว่าเฉพาะ Alice เท่านั้นที่จะสามารถใช้จ่ายบิตคอยน์ของเธอได้ ดังนั้นหัวข้อนี้จะเป็นเนื้อหาของบทถัดไป