疑惑的根源

相信大家都看過上面的這張圖,這張圖來自中本聰的比特幣白皮書,用來介紹比特幣的交易。在這張圖的上面,中本聰寫下了這樣幾句話:We define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership. 為了保持原汁原味,我就不翻譯了,相信大家這點閱讀理解的功力還保留得住。
但問題是,他說的每一句話我都看懂了,可我還是看不明白這張圖到底在說什么。也許比特幣的交易在中本聰的腦海里就是這個樣子,所以我一直懷疑他是不是潛伏在地球的外星人,腦回路跟地球人不一樣,包括那個UTXO的概念,實在有悖於人類正常的思維。所以,這篇文章的任務就是盡量將這張圖轉換成地球人容易理解的形式。
交易的構成

如上圖所示,這是一個比特幣交易的簡化結構(忽略了一些參數,簡化了id)。
在介紹交易結構之前,先簡單說一下比特幣的UTXO概念,方便零基礎的讀者閱讀。在比特幣系統中,沒有余額的概念,只有UTXO(Unspent Transaction Output),即未花費的交易輸出。翻譯成大白話就是我花的錢都是別人給我的錢,而且花的時候必須花完。比如我有一個2 BTC(比特幣) 的UTXO,我想給你1 BTC,我就必須把這個 2BTC的UTXO 花掉,然后生成兩個UTXO,一個 1BTC 的UTXO給你,一個UTXO(小於1 BTC,差值為交易費)給我自己。想看余額怎么辦,就查一下這個地址里有多少UTXO,把里面的BTC加起來就是余額。
好了,現在我們來看交易的結構。比特幣的交易主要由兩部分構成:輸入(input)和輸出(output)。
Input是要說明我打算花的這UTXO是從哪兒來的。
具體的參數包括:
txid : 引用的UTXO所在的那筆交易ID
vout : 引用的UTXO所在交易的輸出中的序號(從0開始)
scriptSig : 解鎖腳本,包含付款人對本次交易的簽名(<sig>)和付款人公鑰(<PubK(A)>)。
Output是要說明我打算生成幾個UTXO,分別給誰,每個UTXO里面有多少BTC。
具體的參數包括:
value : 比特幣數量
n : UTXO序號(從0開始)
scriptPubkey : 鎖定腳本,包含命令(OP_DUP等)和收款人的公鑰哈希(<PubKHash(B)>)。
了解了交易的結構之后,現在我們通過一個交易的示例,來看看簽名和驗證是如何進行的。
交易的簽名

我們來看上面這張圖,A通過交易001轉給B 1BTC,B通過交易002轉給C 1BTC,簡化起見,忽略交易費的問題。
我們來重點分析交易002。
B 打算轉給C 1BTC,他先找到A轉給他的那個UTXO,即交易001的out中n=0的那個UTXO,把相關參數寫入交易002中的in。然后在out中輸入比特幣數量,UTXO序號,鎖定腳本。鎖定腳本中的命令都是固定的,C的公鑰哈希(<PubKHash(C)>)可通過C的錢包地址解碼獲得。
這樣,交易002相關的數據都已經准備好了,就差最后的簽名了。這個簽名就類似於開支票時的簽名,證明我同意把這筆錢給你。但是具體的實現要比簽個字復雜很多,原因就在於互聯網中一切都是可以復制的,如何證明你擁有這筆錢,如何證明這個交易是你創建的且沒有被修改過,這背后都有嚴密的數學理論和算法來保證。
我們先來看下簽名的過程:
簽名的輸入:
1. 待簽名的交易數據(輸入和輸出),即<tx002>。
2. 引用的UTXO相關信息(交易ID、序號、鎖定腳本)
3. B的私鑰,即<PriK(B)>。
4. 簽名類型
簽名的輸出:
1. scriptSig ,即解鎖腳本,包含簽名(<sig>)和 B的公鑰(<PubK(B)>)。
至此,一個完整的交易即創建成功,可以發送給其它節點驗證了。
這里多說一句,細心的讀者可能會發現,輸入2的信息其實輸入1已經包含了,或者可以根據輸入1查的到,為什么還要單獨列出呢。目前我也沒有找到明確的可信服的解釋,不知道是否還有其它深意。期待大神們的指教。
簽名的驗證
交易發送至其它節點后,其它節點會對其進行驗證,只有驗證通過的交易才會被繼續傳播。交易驗證的項目很多,這里只講關於簽名的驗證。
簽名驗證的目的有兩個:
1. 證明交易所引用的UTXO的確屬於付款人。
具體到本次交易,就是證明交易001的序號為0的UTXO的確是發給B的。
2. 證明交易的所有數據的確是付款人提供的,且未被修改過。
具體到本次交易,就是證明B的確創建了交易002,且交易內的數據未被修改過。
下面我們來看驗證是如何進行的,其實很簡單,就是用解鎖腳本解鎖對應UTXO的鎖定腳本,對應上圖就是橙色線所連接的兩個腳本:
<sig><PubK(B)> OP_DUP OP_HASH160 <PubKHash(B)> OP_EQUALVERIFY OP_CHECKSIG
比特幣腳本的執行基於堆棧模型,遵循從左到右,后入先出的原則。關於堆棧的介紹,文末的參考文章中有比較清晰的圖示,不清楚的讀者可以參考。本文為方便闡釋各步驟的意義,采用文字方式描述。各步操作如下:
1. <sig> <sig> 入棧
2. <PubK(B)> <PubK(B)>入棧
3. OP_DUP 復制位於棧頂的<PubK(B)> ,將副本置於棧頂。
4. OP_HASH160 對位於棧頂的<PubK(B)>副本進行HASH160,<PubK(B)>轉變為<PubKHash(B)>。
5. <PubKHash(B)> <PubKHash(B)>入棧
6. OP_EQUALVERIFY 比較位於棧頂的兩個元素是否相同,若相同則移除這兩個元素,繼續執行。若不同,則中斷執行,返回失敗。
7. OP_CHECKSIG 檢查簽名(注意棧內現有的元素為<sig><PubK(B)>),根據結果返回成功或失敗。
下面我們來分析下每一步的意義,步驟1~6的意義其實很明顯,用B提供的公鑰(<PubK(B)>)進行雙哈希(HASH160),然后與鎖定腳本中的公鑰哈希(<PubKHash(B)>)作對比,相同則返回成功。我們知道公鑰哈希(<PubKHash(B)>)就是A在創建交易時根據B的地址生成的,它就是B的公鑰經過雙哈希運算得來的,所以這一步只要提供了B的公鑰,驗證肯定是成功的。所以,步驟1~6 就相當於A把1 BTC發給了B的郵箱,B拿把鑰匙打開了郵箱,證明了B確實擁有這1 BTC。也就是證明了上文中提到的驗證目的1:證明交易001的序號為0的UTXO的確是發給B的。
比較麻煩的是第7步,很多文章說到這里都只是泛泛而談,或是一筆帶過,我學習到這里的時候真是如墮霧中,四顧茫然啊。現在回過頭再去看一些文章中的表述是非常不准確的。這一步簡單的CHECKSIG操作,實際上蘊含了復雜的密碼學和數學原理,證明的其實不是所有權的問題,而是證明了B的確創建了交易002,且交易內的數據未被修改過,也就是上文中提到的驗證目的2。
那么,CHECKSIG的驗證是如何實現的呢?這里運用了橢圓曲線數字簽名算法(ECDSA:The Elliptic Curve Digital Signature Algorithm ),一種利用橢圓曲線進行數字簽名和驗證的算法。下面將簡單介紹這種算法是如何用來進行比特幣交易的簽名和驗證的。涉及到的數學知識不作深入介紹,感興趣的讀者可參照文末的文章鏈接深入了解。
ECDSA

首先,我們看一下橢圓曲線的形狀,如上圖紅線。我們可以把這個曲線上的點定義一種加法:連接兩點的直線與橢圓曲線的交點相對於X軸的對稱點,即為兩點之和。如圖中的 A+B=C。
A+A時我們取A點的切線與曲線交點相對X軸的對稱點。有了A+A,我們就能很方便的定義出乘法。有了乘法,我們選擇一個基點G,能夠很方便地計算出 K=kG,然而,給定K和G,卻很難計算k(至今沒有有效的算法),這就是橢圓曲線離散對數問題。而橢圓曲線密碼學(ECC Elliptic Curves Cryptography)的安全性正是建立於橢圓曲線離散對數問題的困難性之上。基於此,在ECC中我們定義k為私鑰,K為公鑰。
然后,我們再來看一下基於有限域Fp的橢圓曲線域E(Fp):
y^2 ≡ x^3 + ax + b (mod p)
當:a, b ∈ Fp 且滿足 4a^3+27b^2 ≠ 0 (mod p). , x, y ∈ Fp時,這條曲線上的點的集合P=(x,y)就構成了一個基於有限域Fp的橢圓曲線域E(Fp)。
完整描述一個橢圓曲線域實際需要6個參數:
p:限定有限域邊界的質數
a,b:橢圓曲線的參數
G:基點
n:G的階,nG=O∞
h:余因數,控制點的密度。
可以將橢圓曲線域簡單理解為只取橢圓曲線上的那些整數點,但是由於多了一步模運算,因此展示出的形狀與之前的平滑曲線是有差別的(如下圖),但是之前定義的加法乘法規則是不變的。

好了,有了這些概念,我們現在來看一下簽名和驗證的過程:
簽名者的密鑰對:(d, Q);(d為私鑰,Q為公鑰)
待簽名的信息:M;
簽名:Signature(M) = ( r, s)
簽名過程:
1、根據ECC算法隨機生成一個密鑰對(k, R), R=(xR, yR)
2、令 r = xR mod n,如果r = 0,則返回步驟1
3、計算 H = Hash(M)
4、按照數據類型轉換規則,將H轉化為一個big endian的整數e
5、s = k^-1 (e + rd) mod n,若s = 0, 則返回步驟1
6、輸出的S =(r,s)即為簽名。
驗證過程:
1、 計算 H = Hash(M)
2、按照數據類型轉換規則,將H轉化為一個big endian的整數e
3、計算 u1 = es^-1 mod n, u2 = rs^-1 mod n
4、計算 R = (xR, yR) = u1G + u2Q, 如果R = 零點,則驗證該簽名無效
5、令 v = xR mod n
6、若 v == r,則簽名有效,若 v ≠ r, 則簽名無效。
從數學上可以證明,若 v == r,即可證明信息M的確為持有密鑰對(d, Q)的簽名者所簽署,且未被修改過。
上述過程中的Q、S、R均為橢圓曲線域中的點。
我們把上述示例中的輸入參數與比特幣交易002中的參數做個對比:

如上圖所示,可以看出這兩個過程中的參數是一一對應的,我們把交易002中相關的參數按照示例做相應的操作,就不難理解簽名和驗證的具體過程了。由於數學原理上的保證,若簽名驗證成功,即可證明B的確創建了交易002,且交易內的數據未被修改過,也就是上文中提到的驗證目的2。
好了,以上就是筆者目前關於比特幣交易中簽名和驗證的全部理解和思考,水平有限,不免存在謬誤和紕漏,歡迎各位大神批評指正!
ECDSA 部分的兩張動圖引自參考文章,對原作者Nick Sullivan表示感謝,同時對所有參考文章的作者表示感謝!
參考文章
[1] 深入比特幣原理(四)——鎖定腳本(locking script)與解鎖腳本(unlocking script)
https://bbs.huaweicloud.com/blogs/d4c97558190611e89fc57ca23e93a89f
[2] 比特幣交易的數據結構與簽名類型
https://blog.csdn.net/awewong/article/details/78310017
[3] 橢圓曲線密碼學的簡單介紹
https://zhuanlan.zhihu.com/p/26029199
[4] 比特幣系統采用的公鑰密碼學方案和ECDSA簽名算法介紹——第一部分:原理
http://www.8btc.com/btc_ecc_dsa_a