比特幣協議雜談(2)
阿新 • • 發佈:2019-02-19
比特幣協議
- 通用標準:
- 1 hashes
- 比特幣中的雜湊需要被被計算兩次。
- 雜湊演算法,大多數:SHA-256; ROPEMD-160 在需要較短雜湊時被使用(例如: 當建立一個比特幣地址),ROPEMD-160: 先計算 sha-256, 再對上一個雜湊結果計算 ripemd-160s。
- 2 merkle 樹
- merkle樹是二進位制雜湊樹, 在比特幣中使用double sha-256雜湊;
- 如果當形成一個樹的子節點,並且有奇數個元素, 最後的雜湊元素被複制,確保含有偶數個元素。
- 首先:對區塊的交易位元組流做雜湊, 形成底層結構。
- 筆記:
- 1 hashes
- 交易結構
- 表格
大小 欄位 描述 4位元組 版本 明確這筆交易參照的規則 1-9位元組 輸入數量 被包含的輸入的數量 不定 輸入 一個或多個交易輸入 1-9位元組 輸出數量 被包含的輸出的數量 不定 輸出 一個或多個交易輸出 4位元組 時鐘時間 一個UNIX時間戳或區塊號
- 序列化的交易長度
- 版本(4位元組) + 時間戳(4位元組) + 交易輸入Number(var_int) + 交易輸出Number(var_int) + 每個交易的輸入序列化位元組數(var_int) + 每個交易的輸出序列化位元組數(var_int)
- 每個交易的輸入序列化位元組數 = 引用的UTXO雜湊(32位元組) + 引用的輸出索引序列號(4位元組) + 未使用的序列號(4位元組) + 指令碼長度序列化Number(var_int) + 指令碼長度位元組數
- 每個交易的輸入序列化位元組數 = 交易的比特幣總量(8位元組) + 指令碼長度序列化Number(var_int) + 指令碼長度位元組數
- 時間戳:
- 鎖定時間(或時間戳)定義了能被加到區塊鏈裡的最早的交易時間。
- 交易中,被設定為0, 表示立即執行;
- 大於0 小於 5億, 表示該交易所位於區塊在鏈中的高度,意思為: 在這個指定的區塊高度之前,該交易沒有被包含在區塊鏈中。
- 大於 5 億, 表示Unix紀元時間戳,表示在這個指定時間點之前, 該交易沒有被包含在區塊鏈中。 同時意思為:該交易只有到達指定時間, 才可以被新區快打包,進行交易驗證,相當於交易延後執行。
- 交易輸入的結構
- 表格
尺寸 欄位 說明 32個位元組 交易 指向交易包含的被花費的UTXO的雜湊指標 4個位元組 輸出索引 被花費的UTXO的索引號,第一個是0 1–9個位元組 (可變整數) 解鎖指令碼尺寸,用位元組表示的後面的解鎖指令碼長度 變長 解鎖指令碼 一個達到UTXO鎖定指令碼中的條件的指令碼 4個位元組 序列號 目前未被使用的交易替換功能,設成0xFFFFFFFF
- 序列號: 8d3117d26d
- 用來覆蓋在交易鎖定時間之前失效的交易,一專案前沒有在比特幣中用到的功能。
- 大多數交易把這個值設定成最大的整數(0xFFFFFFFF),並且被比特幣網路忽略。
- 如果一次交易有非零的鎖定時間(時間戳),那麼它至少需要有一個序列號比0xFFFFFFFF低的輸入來啟用鎖定時間。
- 交易輸出的結構
- 交易輸出結構
尺寸 欄位 說明 8個位元組 總量 用聰表示的比特幣值(10-8比特幣) 1–9個位元組 (可變整數) 鎖定指令碼尺寸 用位元組表示的後面的鎖定指令碼長度 變長 鎖定指令碼 一個定義了支付輸出所需條件的指令碼
一個完整的交易資料: 結構劃分(該交易為真實的線上交易)
/*
01000000 //交易版本號 4位元組
01 //交易輸入數量 1位元組
33a525c7bb912fea5e4f7633
35ac2afd236bd43c906b5559
f80c0a30c4ccb4b4 //指向的UTXO 雜湊 32位元組
01000000 //UTXO 序號 4 位元組
6a //指令碼位元組數 1位元組
4730440220129bfbd49e9fb990cbc542c95cbfe1
f073c5c8d34aae70d2e80b1bc6c3e3aa2202204b //包含:簽名和公鑰
c87a25ce402c7469a2c221cfcb26a95ea95a6142
a733e1ef9e1c242bf5c2d3012103fbf4e8da6848
51fdafdf539deffc424d995b207a8f2bc827c679
366cfcf3489f //指令碼內容 106位元組
ffffffff //sequence 1位元組
01 //交易輸出數量 1位元組
b851950000000000 //輸出金額 8位元組
17 //鎖定指令碼長度 1位元組
a91432f08210748b1b0b00e89953b7db2145c8f0
9d0f87 //鎖定指令碼 23位元組
00000000 //該交易的時間戳
*/
- 區塊的結構:
- 區塊的結構
大小 欄位 描述 4位元組 區塊大小 用位元組表示的該欄位之後的區塊大小 80位元組 區塊頭 組成區塊頭的幾個欄位 1-9 (可變整數) 交易計數器 交易的數量 可變的 交易 記錄在區塊裡的交易資訊 - 區塊頭的結構:
- 大小 欄位 描述
4位元組 版本 版本號,用於跟蹤軟體/協議的更新
32位元組 父區塊雜湊值 引用區塊鏈中父區塊的雜湊值
32位元組 Merkle根 該區塊中交易的merkle樹根的雜湊值
4位元組 時間戳 該區塊產生的近似時間(精確到秒的Unix時間戳)
4位元組 難度目標 該區塊工作量證明演算法的難度目標
4位元組 Nonce 用於工作量證明演算法的計數器
- 父區塊的雜湊值: 一個通過SHA256演算法對父區塊頭進行二次雜湊計算而得到的數字指紋。
- 請注意,區塊雜湊值:
- 實際上並不包含在區塊的資料結構裡,不管是該區塊在網路上傳輸時,抑或是它作為區塊鏈的一部分被儲存在某節點的永久性儲存裝置上時。
- 相反,區塊雜湊值是當該區塊從網路被接收時由每個節點計算出來的。
- 區塊的雜湊值可能會作為區塊元資料的一部分被儲存在一個獨立的資料庫表中,以便於索引和更快地從磁碟檢索區塊。
- Base58Check:
- 1 詳細解釋:
- 增加了錯誤校驗碼來檢查資料在轉錄中出現的錯誤。
- 校驗碼長4個位元組,新增到需要編碼的資料之後。校驗碼是從需要編碼的資料的雜湊值中得到的,所以可以用來檢測並避免轉錄和輸入中產生的錯誤。
- 2 為了使用Base58Check編碼格式對資料進行編碼:
- 1 首先進行字首新增
- 首先我們要對資料新增一個稱作“版本位元組”的字首,這個字首用來明確需要編碼的資料的型別。
- 例如:對比特幣地址編碼時,比特幣地址的字首是0(十六進位制是0x00)
- 而對私鑰編碼時字首是128(十六進位制是0x80)
- 指令碼地址的字首是 0x05
- 2 計算校驗碼
- 採用雙雜湊,對之前的結果(字首和資料)執行兩次SHA256雜湊演算法。
checksum = SHA256(SHA256(prefix+data))
- 取上步計算雜湊結果的前4個位元組作為校驗碼。
- 校驗碼會新增到資料之後。
- 採用雙雜湊,對之前的結果(字首和資料)執行兩次SHA256雜湊演算法。
- 1 首先進行字首新增
- 3 結果的組成:
- 三部分組成:字首、資料和校驗碼。
- 三部分組成:字首、資料和校驗碼。
- 2 為了使用Base58Check編碼格式對資料進行編碼:
比特幣的地址生成:
- 增加了錯誤校驗碼來檢查資料在轉錄中出現的錯誤。
- 生成方式: 由公鑰 –》 sha256(公鑰) –》 RIPEMD160(上步結果) –》base58check(上步結果) === 比特幣地址;
- 示例: 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy; 長度為34個位元組, 該為Base58check編碼後的字串。
- 1 詳細解釋:
比特幣網路
- 節點採用TCP協議、使用8333埠.
校驗交易
- 打包交易前,校驗如何打包交易最合算。
打包交易Mempool:
- 打包交易前的校驗演算法, 主要資料來源.
比特幣交易的集中方式:
- P2PKH: 此類交易都含有一個鎖定指令碼,該指令碼由公鑰雜湊實現阻止輸出功能.支付給公鑰雜湊的交易
- 由P2PKH指令碼鎖定的輸出可以通過鍵入公鑰和由相應私鑰創設的數字簽名得以解鎖。
- 由P2PKH指令碼鎖定的輸出可以通過鍵入公鑰和由相應私鑰創設的數字簽名得以解鎖。
- P2PK: 支付給公鑰的交易
- 主要目的一方面為使比特幣地址更簡短,另一方面也使之更方便使用。
- 多重簽名交易:
- 支付給多個
- P2SH: 複雜的鎖定指令碼被電子指紋所取代,電子指紋為密碼學雜湊。
- 1 在現金交易中的鎖定指令碼是這種: 確保在贖回交易中提供的指令碼的雜湊,是這個建立地址的指令碼的雜湊。
- P2PKH: 此類交易都含有一個鎖定指令碼,該指令碼由公鑰雜湊實現阻止輸出功能.支付給公鑰雜湊的交易
mempool中的交易:
- 1 通過檢查後的合法交易
- 2 檢查的內容: 交易物件合法, 指令碼是否合法, 是否放入區塊,
- 3 交易本身的驗證, 交易鏈的驗證—》 全在已經確認的block裡面,
- 4 交易與block的互動:
- 交易扔給一個交易雜湊, block返回一個交易的內容
- 5 檢測交易的兩種路徑: 檢查已確認的區塊, 檢查mempool。
- 6 優化方案:
交易資料長度問題:
- 1 交易資料長度分為4個等級:
- 1 交易資料長度 < 253 byte; 使用1個位元組表示交易長度。該位元組直接表示接下來交易的資料長度,它後面緊跟交易資料;(即:在標識交易長度方面,使用了1個位元組,該位元組直接表示交易的長度)
- 2 交易資料長度 < 65535 byte; 使用一個位元組 0xfd 作為標識,接下來的兩個位元組為交易資料的長度,再接下來為交易資料;(即:在標識交易長度方面:使用了3個位元組,1個為標識,2個為實際資料)
- 3 交易資料長度 <= 4294967295 byte; 使用一個位元組 0xfe 作為標識,接下來的四個位元組為交易資料的長度,再接下來為交易資料;(即:在標識交易長度方面:使用了5個位元組,1個為標識,4個為實際資料)
- 4 交易資料長度 > 4294967295 byte; 使用使用一個位元組 0xfe 作為標識,接下來的八個位元組為交易資料的長度,再接下來為交易資料;(即:在標識交易長度方面:使用了9個位元組,1個為標識,8個為實際資料)
- 1 交易資料長度分為4個等級:
創幣交易(coinbase 交易):
- 與常規交易不同,創幣交易沒有輸入,不消耗UTXO。它只包含一個被稱作coinbase的輸入,僅僅用來建立新的比特幣。創幣交易有一個輸出,支付到這個礦工的比特幣地址。
指令碼資料長度:
- 指令碼資料長度分為 5 個等級:
- 1 dataLenth = 0, OpCode = OP_0
- 2 dataLenth = 1, OpCode = OP_1 – OP_16, data= 1–16
- dataLenth = 1, OpCode = OP_1NEGATE
- 3 dataLenth <= 75, OpCode = dataLenth
- 4 dataLenth <= 255, OpCode = OP_PUSHDATA1
- 5 dataLenth <= 65535, OpCode = OP_PUSHDATA2
- 指令碼資料長度分為 5 個等級:
指令碼操作碼
- 表示長度的操作碼
- 1 OP_0(0x00) 一個空字串被壓入棧
- 2 OP_1,OP_TRUE(0x51) 1 數字1被壓入棧
- 3 OP_2-OP_16(0x52-0x60) 數字(2-16)被壓入棧
- 4 OP_PUSHDATA1(0x4c==76) 接下來一個位元組標識:指令碼資料長度
- 5 OP_PUSHDATA2(0x4d==77) 接下來兩個位元組標識:指令碼資料長度
- 6 OP_PUSHDATA4(0x4e==78) 接下來四個位元組標識:指令碼資料長度
- 5 當指令碼長度 < OP_PUSHDATA1, 該位元組直接表示指令碼資料的長度.
- P2SH 的指令碼概括
- 1 指令碼位元組總數 23 byte
- 2 指令碼的第一個位元組: OP_HASH160 操作碼;
- 3 指令碼的第二個位元組: 0x14(20), 指令碼資料的長度
- 4 指令碼的資料 隨後20個位元組,(內容為指令碼雜湊)
- 5 最後一個位元組 OP_EQUAL 操作碼
- 指令碼資料操作
- 1 OP_EQUAL : 輸入相等返回1,否則返回 0;
- 2 OP_HASH160(169) : 輸入雜湊兩次,第一次sha-256,第二次ripemd-160;
- 數字替代操作符
- 1 0x80 : 可以用來表示指令碼資料值為:負零
- 2 0x81 : 可以用來表示指令碼資料值為:-1
- 對應的操作碼 : OP_1NEGATE, -1入棧
- 3 false : 用來表示任何 零。
- 4 true : 用來標識任何 非零。
- 5 正 0 : 通過0長度向量表示
- 保留字(使用保留字將導致交易失敗)
- 1 OP_RESERVED, 導致交易失敗,除非在為執行的條件分支中。
- 控制流
- 1 OP_IF (0x63)
- 2 OP_NOTIF (0x64)
- 3 OP_VERIF (0x65) 保留字,當出現在未執行的分支中,導致交易失敗
- 4 OP_VERNOTIF (0x66) 保留字,當出現在未執行的分支中,導致交易失敗
- 5 OP_ELSE (0x67)
- 6 OP_ENDIF (0x68)
- 7 OP_NOP (0x61) Do Nothing。
- 時間戳
- 1 OP_CHECKLOCKTIMEVERIFY (0xb1)
- 描述:標識交易無效:如果棧頂項大於交易時間戳欄位,否則,指令碼繼續執行,就像執行OP_NOP一樣。交易無效 : 1.棧頂為空;2.棧頂項為負;3.棧頂項大於等於 5億,然而交易時間戳欄位小於5億,或者相反的情況,都算失敗; 4.交易輸入序列欄位等於0xffffffff.
- 2 OP_CHECKSEQUENCEVERIFY (0xb2)
- 描述:標識交易無效:如果交易輸入的相對鎖定時間不等於或大於棧頂項的值。(交易輸入nSequence欄位:由BIP68強制執行)
- 1 OP_CHECKLOCKTIMEVERIFY (0xb1)
- 指令碼限制:
- 1 每個指令碼最大操作碼個數為 : 201
- 2 OP_16 : 什麼東西?
- 3 指令碼的最大位元組為 10000 位元組。
- 表示長度的操作碼
簽名的限制:
- 1 長度範圍:9 <= size <= 73
公鑰格式:
- 壓縮公鑰
- lenth = 33, 並且首位元組為 0x02 或者 0x03
- 未壓縮公鑰
- lenth = 65, 並且首位元組為 0x04
- 其他都為格式錯誤的公鑰
- 壓縮公鑰
閃電網路
- 閃電網路的關鍵技術有三,後後依賴於前前,依次是:RSMC(序列到期可撤銷合約),HTLC(雜湊時間鎖定合約)和閃電網路。
- RSMC
- 1 閃電網路的基礎是交易雙方之間的雙向微支付通道,RSMC定義了該雙向微支付通道的最基本工作方式。
- 2 微支付通道中沉澱了一部分資金,通道也記錄有雙方對資金的分配方案。通道的設立會記錄在比特幣區塊鏈上。
- 3 為了鼓勵雙方儘可能久地利用通道進行交易,RSMC對主動終止通道方給予了一定的懲罰:主動提出方其資金到賬將比對方晚,因此誰發起誰吃虧。
- 4 通道餘額分配方案的本質是結算準備金。在此安排下,因為要完全控制資金交收風險,每筆交易都不能突破當前結算準備金所施限制。
Txmessage 驗證
- 1 自我格式校驗
- 2 鏈上的校驗
- 1 從mempool取 preOut Tx, 判斷該交易此時的狀態
- 0 mempool 為未確認的交易
- 1 orPhen 交易
- 2 OK的交易(已經驗證過的)
- 2 從blockChain 取preOut Tx,(這個應該去掉)
- 3 UTXO中
- 0 UTXO 中都為已經確認的交易
- 1
- 1 從mempool取 preOut Tx, 判斷該交易此時的狀態
- 3 Tx需要新增一個狀態,是否為孤立交易。
- blockMessage 驗證
進入mempool的交易:
- 1 打包的交易
指令碼包含的內容:
- 1 簽名指令碼包含兩部分: 簽名和公鑰
- 公鑰必須匹配上個交易輸出鎖定指令碼中的公鑰雜湊, 同時這個公鑰被用來驗證 當前的簽名。
- 簽名指令碼總位元組數為:106byte;
- 其中:交易簽名:72 byte; 公鑰:33 byte; 再加入首位元組 0x47,
- 1 簽名指令碼包含兩部分: 簽名和公鑰
特殊的交易輸出,導致的特殊結果
- 1 在鎖定指令碼中,標記交易為不可花費採用的是如下這種形式(即:任何人都不可以花費這筆交易,同時這筆交易也不會被計入UTXO集):
- scriptPubkey : OP_RETURN
- 2 任何人都可以花費的交易
- 1 在鎖定指令碼中,標記交易為不可花費採用的是如下這種形式(即:任何人都不可以花費這筆交易,同時這筆交易也不會被計入UTXO集):
簽名型別:
- 1 簽名分為三類: SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE。
- 2 SIGHASH_ALL:
- 預設型別,目前絕大部分交易採用的,即:籤整單交易。
- 組織所有輸出、輸入,就像上文分解Hex過程一樣,每個輸入都對應一個簽名,暫時留空,其他包括sequence等欄位均須填寫,這樣就形成了一個完整的交易Hex(只缺簽名欄位)。然後,每一個輸入均需使用私鑰對該段資料進行簽名,簽名完成後各自填入相應的位置,N個輸入N個簽名。簡單理解就是:對於該筆單子,認可且只認可的這些輸入、輸出,並同意花費我的那筆輸入。
- 3 SIGHASH_SINGLE:
- 該簽名型別其次自由鬆散,僅對自己的輸入、輸出簽名,並留空sequence欄位。其輸入的次序對應其輸出的次序,比如輸入是第3個,那麼簽名的輸出也是第三個。簡單理解就是:我同意花費我的那筆錢,且只能花費到我認可的輸出,至於單子裡的其他輸入、輸出,我不關心。
- 4 SIGHASH_NONE
- 該簽名型別是最自由鬆散的,僅對輸入簽名,不對輸出簽名,輸出可以任意指定。某人對某筆幣簽名後交給你,你可以在任意時刻填入任意接受地址,廣播出去令其生效。簡單理解就是:我同意花費我的那筆錢,至於給誰,我不關心。
交易輸入簽名的生成:
- 交易簽名使用命令:
signrawtransaction <hex string> \
[{"txid":txid,"vout":n,"scriptPubKey":hex,"redeemScript":hex},...] [<privatekey1>,...] \
[sighashtype="ALL"]
- 第一個引數是建立的待簽名交易的十六進位制字串;
- 第二個引數有點類似建立交易時的引數,不過需要多出一個公鑰欄位scriptPubKey,其他節點驗證交易時是通過公鑰和簽名來完成的,所以要提供公鑰;如果是合成地址,則需要提供redeemScript
- 第三個引數是即將花費的幣所在地址的私鑰,用來對交易進行簽名,如果該地址私鑰已經匯入至bitcoind中,則無需顯式提供;
- 第四個引數表示簽名型別,三種交易簽名型別;
- 該命令輸出:
- 輸出為完整的交易16進位制字串,內容為一個交易完整的交易結構,簽名內容會自動填充進 簽名欄位。
- 解析輸出命令:
- bitcoind decoderawtransaction
- : 簽名命令後輸出的內容。
- 交易簽名使用命令:
- 建立待發送交易:
- 建立待發送交易,由命令:createrawtransaction [{“txid”:txid,”vout”:n},…] {address:amount,…}來完成。
- 建立一筆:將 0.1 BTC傳送至 1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x ,並支付 0.0001 BTC做為礦工費。輸入交易的額度為 0.199 ,輸出為 0.1 + 0.0001 = 0.1001 ,那麼還剩餘: 0.199 - 0.1001 = 0.0989 ,將此作為找零發回給自己。
- 執行命令:
bitcoind createrawtransaction \
'[{"txid":"296ea7bf981b44999d689853d17fe0ceb852a8a34e68fcd19f0a41e589132156","vout":0}]' \
'{"1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x":0.1, "1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3":0.0989}'
- 改命令輸出:16進位制字串。內容為不包含交易簽名的整個完整交易
- 通過命令:decoderawtransaction ,可以將此段十六進位制字串解碼,輸出如下結構資訊。
{
"txid" : "54f773a3fdf7cb3292fc76b46c97e536348b3a0715886dbfd2f60e115fb3a8f0",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid" : "296ea7bf981b44999d689853d17fe0ceb852a8a34e68fcd19f0a41e589132156",
"vout" : 0,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.10000000,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 fdc7990956642433ea75cabdcc0a9447c5d2b4ee OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914fdc7990956642433ea75cabdcc0a9447c5d2b4ee88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1Q8s4qDRbCbFypG5AFNR9tFC57PStkPX1x"
]
}
},
{
"value" : 0.09890000,
"n" : 1,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 d6c492056f3f99692b56967a42b8ad44ce76b67a OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914d6c492056f3f99692b56967a42b8ad44ce76b67a88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1Lab618UuWjLmVA1Q64tHZXcLoc4397ZX3"
]
}
}
]
}
case :
hashType & 0x1f:
case : 0x10 == 2 (SIGHASH_NONE)
case : 0x11 == 3 (SIGHASH_SINGLE)
case : 0x01 == 1 (SIGHASH_ALL)
//一個完整的交易
type Tx struct {
Hash utils.Hash //本交易的雜湊
LockTime uint32 //交易時間戳
Version int32 //版本號
Ins []*TxIn //交易輸入
Outs []*TxOut //交易輸出
}
//交易輸入
type TxIn struct {
PreviousOutPoint *OutPoint //引用的UTXO
ScriptSig []byte //簽名指令碼
Sequence uint32 //todo ?
}
//指向輸入中引用的UTXO
type OutPoint struct {
Hash *utils.Hash //該 UTXO 交易的雜湊
Index uint32 //該 UTXO的索引號
}
//交易輸出
type TxOut struct {
Value int64 //交易幣值
OutScript []byte //鎖定指令碼
}
// digest represents the partial evaluation of a checksum.
// 摘要表示對校驗和的部分求值
type digest struct {
h [8]uint32
x [chunk]byte
nx int
len uint64
is224 bool // mark if this digest is SHA-224
}
//解析後的指令碼結構
type ParsedOpCode struct {
opValue byte //操作碼
length int //四種情況, 1:data為nil; -1:資料長度用一個位元組標識;-2:資料長度用兩個位元組標識;-4:資料長度用四個位元組標識
data []byte //指令碼資料
}
3Bzt3JA9eebS2FcgGpSSQDETmewhJUWjFs
//
type ScriptFreeList chan []byte
var scriptPool ScriptFreeList = make(chan []byte, FreeListMaxItems)