官术网_书友最值得收藏!

2.1 密碼學

密碼學知識被廣泛用于安全的信息通信、數據存儲、交易驗證等方面,也是區塊鏈最基礎的技術之一。這些知識包括對信息的轉換、加解密,以及校驗過程,也包括以太坊地址和交易Hash,交易信息RLP編碼、基于橢圓曲線公私鑰簽名、區塊Merkle樹交易等。

2.1.1 Hash

Hash,在數學上也被稱為“散列”,把任意長度的輸入,通過Hash算法,變換成固定長度的輸出,該輸出就是Hash值。這種轉換其實是一種壓縮映射,也就是,Hash值的空間通常遠小于輸入的空間,稍微不同的輸入,可能產生差異很大的輸出值,當然不同的輸入有時候也可能產生相同的輸出,出現相同輸出值的概率就叫Hash的碰撞率。本質上Hash是一種將不同信息通過一種恰當的方法產生消息摘要,便于在后續使用時得到較好的識別。見圖2-1。

圖2-1 Hash數學模型

Hash函數的實現,經過多年的發展已經有非常多的種類和實現,有的強調實現簡單快速,有的注重Hash結果的較小的碰撞率,還有的關注算法復雜實現較高的安全性,總之要根據不同的應用場景,選擇適當的Hash算法。一些知名的Hash函數包括MD5、SHA系列,以及PBKDF2等。

SHA(Secure Hash Algorithm, SHA)家族的系列Hash算法,是由美國國家安全局(NSA)設計,并由美國國家標準與技術研究院(NIST)發布,在全球范圍內被廣泛使用的安全雜湊算法,常常應用在數字簽名和校驗的場景中,其中尤以SHA1使用非常廣泛。但是,2005年2月,山東大學王小云等發表了完整版對SHA-1的攻擊,只需少于2的69次方的計算復雜度,就能找到一組碰撞,根據摩爾定律,隨著計算機計算能力的提高,SHA1很快就被認為一種不安全的Hash算法。這之后,NIST又相繼發布了SHA-224、SHA-256、SHA-384和SHA-512,后四者都稱為SHA-2; SHA-256和SHA-512是很新的雜湊函數,實際上二者結構是相同的,只在循環執行的次數上有所差異。SHA-224和SHA-384則是前述二種雜湊函數的截短版,利用不同的初始值做計算。

隨著硬件設備計算算力不斷攀升,研究破解攻擊SHA系列算法的方法和可能性也越來越高,于是美國國家標準技術研究所(NIST)在2005年、2006年分別舉行了兩屆密碼Hash研討會;同時于2007年正式宣布在全球范圍內征集新的下一代密碼Hash算法,舉行SHA-3競賽。新的Hash算法將被稱為SHA-3,并且作為新的安全Hash標準,增強現有的FIPS 180-2標準。2010年12月9日,NIST通過討論評選出了第二輪勝出的五個算法,此次被選出的五個算法將進入第三輪的評選,也就是最后一輪的評選,這五個算法分別是:BLAKE、Gr?stl、JH、Keccak、Skein。2012年10月2日最終宣布Keccak確定為SHA-3的獲勝Hash算法。Keccak算法由意法半導體的Guido Bertoni、Joan Daemen(AES算法合作者)和Gilles Van Assche,以及恩智浦半導體的Micha?l Peeters聯合開發,它與SHA-2在設計上有極大差別,適用于SHA-2的攻擊方法將不能作用于Keccak。

Keccak算法采用的海綿結構如圖2-2所示,其中M為任意長度的消息,Z為Hash后的輸出,f[b]為置換函數,r為比特率,c為容量,且b=r+c,參數c為Hash輸出長度的2倍,即c=2n。從圖中可以明顯看出,海綿結構有兩個階段:absorbing(吸收)階段和squeezing(壓縮)階段。在absorbing階段,消息M首先被填充,然后分組,每組有r個比特,同時b個初始狀態全部初始化為0。填充規則為在消息M后串聯一個數字串100…01,其中0的個數是使得消息M填充后長度為r的最小正整數倍。根據此規則可以發現最小填充的長度為2,最大為r+1。海綿結構的工作原理是先輸入我們要計算的串,然后對串進行一個填充,將輸入串用一個可逆的填充規則填充并且分塊,分塊后就進行吸水的階段,當處理完所有的輸入消息結構以后,海綿結構切換到擠壓狀態,擠壓后輸出的塊數可由用戶任意選擇。

圖2-2 Keccak算法海綿結構圖

由于Keccak采用了不同于之前SHA1/2的MD(Merkel Damgard)結構,所以針對MD結構的攻擊對Keccak不再有效,因此到目前為止,還沒有出現能夠對實際運用中的Keccak算法形成威脅的攻擊方法。

以太坊沿用了比特幣在Hash運算上相同的Keccak算法,生成32個字節256位的摘要信息。以go客戶端代碼實現分析,使用的是f [1600] 函數,f [b] 中的每一輪包含5個步驟,總共循環24輪。

            func keccakF1600(a *[25]uint64) {
        // Implementation translated from Keccak-inplace.c
        // in the keccak reference code.
        var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64

        for i := 0; i < 24; i += 4 {
            // Combines the 5 steps in each round into 2 steps.
            // Unrolls 4 rounds per loop and spreads some steps across rounds.

            // Round 1
            bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
            bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
            bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
            bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
            bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
            d0 = bc4 ^ (bc1<<1 | bc1>>63)
            d1 = bc0 ^ (bc2<<1 | bc2>>63)
            d2 = bc1 ^ (bc3<<1 | bc3>>63)
            d3 = bc2 ^ (bc4<<1 | bc4>>63)
            d4 = bc3 ^ (bc0<<1 | bc0>>63)

            bc0 = a[0] ^ d0
            t = a[6] ^ d1
            bc1 = t<<44 | t>>(64-44)
            t = a[12] ^ d2
            bc2 = t<<43 | t>>(64-43)
            t = a[18] ^ d3
            bc3 = t<<21 | t>>(64-21)
            t = a[24] ^ d4
            bc4 = t<<14 | t>>(64-14)
            a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i]
            a[6] = bc1 ^ (bc3 &^ bc2)
            a[12] = bc2 ^ (bc4 &^ bc3)
            a[18] = bc3 ^ (bc0 &^ bc4)
            a[24] = bc4 ^ (bc1 &^ bc0)

            t = a[10] ^ d0
            bc2 = t<<3 | t>>(64-3)
            t = a[16] ^ d1
            bc3 = t<<45 | t>>(64-45)
            t = a[22] ^ d2
            bc4 = t<<61 | t>>(64-61)
            t = a[3] ^ d3
            bc0 = t<<28 | t>>(64-28)
            t = a[9] ^ d4
            bc1 = t<<20 | t>>(64-20)
            a[10] = bc0 ^ (bc2 &^ bc1)
            a[16] = bc1 ^ (bc3 &^ bc2)
            a[22] = bc2 ^ (bc4 &^ bc3)
            a[3] = bc3 ^ (bc0 &^ bc4)
            a[9] = bc4 ^ (bc1 &^ bc0)

            t = a[20] ^ d0
            bc4 = t<<18 | t>>(64-18)
            t = a[1] ^ d1
            bc0 = t<<1 | t>>(64-1)
            t = a[7] ^ d2
            bc1 = t<<6 | t>>(64-6)
            t = a[13] ^ d3
            bc2 = t<<25 | t>>(64-25)
            t = a[19] ^ d4
            bc3 = t<<8 | t>>(64-8)
            a[20] = bc0 ^ (bc2 &^ bc1)
            a[1] = bc1 ^ (bc3 &^ bc2)
            a[7] = bc2 ^ (bc4 &^ bc3)
            a[13] = bc3 ^ (bc0 &^ bc4)
            a[19] = bc4 ^ (bc1 &^ bc0)

            t = a[5] ^ d0
            bc1 = t<<36 | t>>(64-36)
            t = a[11] ^ d1
            bc2 = t<<10 | t>>(64-10)
            t = a[17] ^ d2
            bc3 = t<<15 | t>>(64-15)
            t = a[23] ^ d3
            bc4 = t<<56 | t>>(64-56)
            t = a[4] ^ d4
            bc0 = t<<27 | t>>(64-27)
            a[5] = bc0 ^ (bc2 &^ bc1)
            a[11] = bc1 ^ (bc3 &^ bc2)
            a[17] = bc2 ^ (bc4 &^ bc3)
            a[23] = bc3 ^ (bc0 &^ bc4)
            a[4] = bc4 ^ (bc1 &^ bc0)

            t = a[15] ^ d0
            bc3 = t<<41 | t>>(64-41)
            t = a[21] ^ d1
            bc4 = t<<2 | t>>(64-2)
            t = a[2] ^ d2
            bc0 = t<<62 | t>>(64-62)
            t = a[8] ^ d3
            bc1 = t<<55 | t>>(64-55)
            t = a[14] ^ d4
            bc2 = t<<39 | t>>(64-39)
            a[15] = bc0 ^ (bc2 &^ bc1)
            a[21] = bc1 ^ (bc3 &^ bc2)
            a[2] = bc2 ^ (bc4 &^ bc3)
            a[8] = bc3 ^ (bc0 &^ bc4)
            a[14] = bc4 ^ (bc1 &^ bc0)

            // Round 2

為了實現高性能,在arm處理器上Keccak完全使用匯編實現,使用go語言封裝。

以太坊go客戶端,在crypto加密包中,對外封裝了使用接口,用來生成Hash值。

        // Keccak256 calculates and returns the Keccak256 Hash of the input data.
        func Keccak256(data ...[]byte) []byte {
            d := sha3.NewKeccak256()
            for _, b := range data {
                d.Write(b)
            }
            return d.Sum(nil)
        }

        // Keccak256Hash calculates and returns the Keccak256 Hash of the input data,
        // converting it to an internal Hash data structure.
        func Keccak256Hash(data ...[]byte) (h common.Hash) {
            d := sha3.NewKeccak256()
            for _, b := range data {
                d.Write(b)
            }
            d.Sum(h[:0])
            return h
        }

在應用中,只需要調用crypto.Keccak256Hash或者crypto.Keccak256Hash := crypto. Keccak256Hash([]byte(“hello world”))即可獲取指定輸入的Hash輸出。

在以太坊中有大量的信息以Hash摘要的形式呈現,例如賬戶和合約地址、交易Hash、區塊Hash、事件Hash。下面這個代碼就是實現交易的Hash,將交易transaction數據進行rlp編碼后,再做Keccak256 Hash運算,最后得到32字節的交易Hash值:0x25e18c91465 c6ee0f79e45016c5dd55eb12424c5d91e59eed237039ba4b239be。

        // Hash Hashes the RLP encoding of tx.
        // It uniquely identifies the transaction.
        func (tx *Transaction) Hash() common.Hash {
            if Hash := tx.Hash.Load(); Hash ! = nil {
                return Hash.(common.Hash)
            }
            v := rlpHash(tx)
            tx.Hash.Store(v)
            return v
        }
        func rlpHash(x interface{}) (h common.Hash) {
            hw := sha3.NewKeccak256()
            rlp.Encode(hw, x)
            hw.Sum(h[:0])
            return h
        }

2.1.2 橢圓曲線的加解密

密碼學在工程應用時,一般分為兩種加解密方式。一種是對稱加密,比如DES、AES,這種加密算法是加解密相關方共享一份密鑰,一方加密,另外一方解密,很多應用的密碼或者關鍵信息,都是通過AES加密算法運算存儲或者傳輸的,這種加密算法有個比較突出的優點在于運算相對簡單,性能損耗小,效率高。另外一種加密方式叫非對稱加密,加解密雙方共享不同的密鑰,當加密方使用私鑰加密時,解密方必須使用對應的公鑰解密,比較典型的有RSA和橢圓曲線ECC加解密算法。

RSA(由Ron Rivest、Adi Shamir、Leonard Adleman三個發明人姓氏的首字母組成)基于一個十分簡單的數論事實:將兩個大質數相乘十分容易,但是想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰,只要密鑰長度足夠長,破解是非常困難的。RSA算法,公鑰用來公開并加密,私鑰用來保留解密,且不可互換,更多應用在加密密鑰協商、加密證書簽名的場景中。我們常見的https協議,就是采用RSA作為前期交換對稱密鑰的非對稱安全加解密算法。

橢圓曲線ECC和簽名ECDSA在數字貨幣和區塊鏈的應用中被普遍采用。ECC(Ellipse Curve Cryptography)與基于大質數因子分解困難性的加密方法不同,依賴的數學原理是求解橢圓曲線離散對數問題的困難性。

一個通用橢圓曲線的表達式,可以描述為:

Ax3+Bx2+Cxy2+Dy3+Ex2+Fxy+Gy2+Hx+Iy+J=0

圖2-3 橢圓曲線

參數不同,描述的橢圓曲線特性不同,差異很大。將一個橢圓曲線用二元三階方程表示:y2= x3+ ax + b,其中ab為系數,同時滿足4a3+27b2≠0.橢圓曲線如圖2-3所示。

一個橢圓曲線群是由曲線上的點和無窮遠點O組成的集合。這是一個加法群,定義為:對于橢圓曲線上不同的兩點PQ,則有P+Q=R,它表示一條通過PQ的直線與橢圓曲線相交于一點(-R), -R關于X軸對稱的點即為R,如圖2-4所示。

圖2-4 橢圓曲線加法運算

對于曲線上的任意一點P,有P+(-P)=0,0是無窮遠點。如果P點的坐標是(x, y),那么-P點的坐標是(x, -y);對于橢圓曲線上的任意一點P,有P+P=2P=R,相對于在P點做一條切線,切線與曲線相交于一點,然后取橢圓曲線上關于該交點的對稱點;特別的,對于點P,如果y=0,那么交點在無窮遠點,則有2P=0.

根據這幾條運算法則定義了橢圓曲線的加法,面臨這樣的一個數學難題,對于橢圓曲線上的點P,其中y≠0,也就是縱坐標不等于0,依據前面定義的加法的計算法則,給定一個整數n,很容易求出Q=nP,也就是nP相加,但是在已知了PQ的條件下求取n則是一個很難的問題。

橢圓曲線上的加密與解密一般是運用定義在有限域Fp上的一種橢圓曲線,在橢圓曲線上的加密與解密算法,可以簡單描述為下面的過程(以以太坊上兩個賬戶Alice和Bob為例)。

1.密鑰的問題

通過選取合適的參數abP建立橢圓曲線EP(a, b),并選取橢圓曲線上的一點G作為基點,Bob選整數K作為私鑰,通過運算H=KG得到橢圓曲線上的另外一個點作為公鑰,然后把EP(a, b), G, H傳給Alice。

2.加密過程

對于要加密的信息m, Alice通過編碼將其變為橢圓曲線上的一個點M。然后Alice選擇一個隨機的數r,在橢圓曲線上計算兩個點:

C1=M+rH

C2=rG

然后Alice把C1和C2發給Bob,可以看出Alice是用Bob的公鑰進行加密的。

3.解密過程

Bob收到消息后就用自己的私鑰進行解密:

C1-KC2=M+r?K?G-K?r?G=M

橢圓曲線相對RSA,具有如下優點:

?安全性能更高;

?160位ECC與1024位RSA、DSA有相同的安全強度;

?處理速度更快;

?在私鑰的處理速度上,ECC遠比RSA、DSA快得多;

?帶寬要求更低;

?存儲空間更小;

?ECC的密鑰尺寸和系統參數與RSA、DSA相比要小得多。

在區塊鏈領域中,以太坊沿用了比特幣采用的橢圓曲線secp256k1,y2=x3+ax+b滿足六元組關系D=(p, a, b, G, n, h):

p = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F

= 2^256-2^32-2^9-2^8-2^7-2^6-2^4-1

a = 0, b = 7

G= (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)

n = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141

h = 01

secp256k1由于其自身構造的特殊性,經過優化實現,比其他曲線性能上提高大概30%,也可以有效抵御破解攻擊。

ECDSA(Elliptic Curve Digital Signature Algorithm)是基于橢圓曲線生成公私鑰進行數字簽名和驗證的算法過程。

下面以以太坊上兩個賬戶Alice和Bob,在以太坊網絡中進行ETH轉賬交易來說明以太坊的交易ECDSA簽名和校驗的過程。

2.1.3 簽名

交易簽名過程:

1)生成一個隨機數,作為生成Alice的私鑰k;

2)K=kG, G是橢圓曲線secp256k1上生成的點,該曲線上所得點K坐標(x, y)經過處理即Alice的公鑰,公鑰經過Hash截斷后得到Alice在以太坊網絡中唯一的賬戶地址。這個過程如圖2-5所示。

圖2-5 以太坊從私鑰到賬戶地址生成過程

3)Alice向Bob轉賬ETH 1000wei,生成交易信息:

        rawTx = {
        nonce: web3.toHex(0),
        gasPrice: web3.toHex(20000000000),
        gasLimit: web3.toHex(100000),
        to: '0x687422eEA2cB73B5d3e242bA5456b782919AFc85',
        value: web3.toHex(1000),
            data: '0xc0de'
        };

4)Alice對轉賬交易tx的rlp編碼的Hash進行簽名,得到簽名結果為(r, s, v)。其中rs是標準簽名的結果,分別為32字節;v=27+(r % 2),可看作簽名結果的一種簡單校驗,作為恢復簽名的recoveryID。以太坊交易簽名生成過程如圖2-6所示。

圖2-6 以太坊交易簽名生成過程

簽名實質上是使用私鑰k對交易摘要進行加密的過程。

交易驗證過程:

?交易校驗者,例如礦工miner接收到原始交易和Alice發起轉賬交易的簽名;

?校驗簽名,并根據簽名和交易Hash恢復出Alice的公鑰Q;

?通過公鑰Q,進行Hash截斷得到Alice的賬戶地址。

?通過比對原始交易中的from域是否和Alice的地址相同。

簽名驗證實質上是使用公鑰Q對交易解密的過程。

以太坊go客戶端代碼,通過cgo實際集成了比特幣的secp256k1庫,go代碼封裝了簽名和根據簽名恢復公鑰。

簽名:

        func Sign(msg []byte, seckey []byte) ([]byte, error) {
            if len(msg) ! = 32 {
                return nil, ErrInvalidMsgLen
            }
            if len(seckey) ! = 32 {
                return nil, ErrInvalidKey
            }
            seckeydata := (*C.uchar)(unsafe.Pointer(&seckey[0]))
            if C.secp256k1_ec_seckey_verify(context, seckeydata) ! = 1 {
                return nil, ErrInvalidKey
            }

            var (
                msgdata    = (*C.uchar)(unsafe.Pointer(&msg[0]))
                noncefunc = C.secp256k1_nonce_function_rfc6979
                sigstruct C.secp256k1_ecdsa_recoverable_signature
            )
            if C.secp256k1_ecdsa_sign_recoverable(context, &sigstruct, msgdata, seckeydata,
        noncefunc, nil) == 0 {
                return nil, ErrSignFailed
            }

            var (
                sig      = make([]byte, 65)
                sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
                recid    C.int
            )
            C.secp256k1_ecdsa_recoverable_signature_serialize_compact(context, sigdata, &recid,
        &sigstruct)
            sig[64] = byte(recid) // add back recid to get 65 bytes sig
            return sig, nil
        }

恢復公鑰:

        func RecoverPubkey(msg []byte, s ig []byte) ([]byte, error) {
            if len(msg) ! = 32 {
                return nil, ErrInvalidMsgLen
            }
            if err := checkSignature(sig); err ! = nil {
                return nil, err
            }

            var (
                pubkey  = make([]byte, 65)
                sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
                msgdata = (*C.uchar)(unsafe.Pointer(&msg[0]))
            )
            if C.secp256k1_ecdsa_recover_pubkey(context, (*C.uchar)(unsafe.
    Pointer(&pubkey[0])), sigdata, msgdata) == 0 {
                return nil, ErrRecoverFailed
            }
            return pubkey, nil
        }

        func checkSignature(sig []byte) error {
            if len(sig) ! = 65 {
                return ErrInvalidSignatureLen
            }
            if sig[64] >= 4 {
                return ErrInvalidRecoveryID
            }
            return nil
        }

公鑰到賬戶地址的轉換:

以太坊中的賬戶地址和比特幣不同,轉換相對簡單。具體來說就是Hash(公鑰)的后20位,這里的Hash算法是sha3-256,可以用代碼來表示:

        crypto.Keccak256(pubKey)[12:]
        func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
            pubBytes := FromECDSAPub(&p)
            return common.BytesToAddress(Keccak256(pubBytes[1:])[12:])
        }

2.1.4 Merkle樹和驗證

Merkle樹又叫哈希樹,是一種二叉樹,由一個根節點、一組中間節點和一組葉節點組成。最下面的葉節點包含存儲數據或其哈希值,每個中間節點是它的兩個孩子節點內容的哈希值,根節點也是由它的兩個子節點內容的哈希值組成,如圖2-7所示。

圖2-7 Merkle樹形結構

Merkle樹的特點在于,底層數據的任何變動都會傳遞到其父親節點,一直到樹根。當葉子節點有數據不同,如果要比較兩個集合的數據是否相同,只需要比較樹根就可以了。因此Merkle樹的典型應用場景包括:

?快速比較大量數據:當兩個默克爾樹根相同時,則意味著所代表的數據必然相同。

?快速定位修改:例如圖2-7中,如果L1中數據被修改,會影響到Hash 0-0, Hash 0和Root。因此,沿著Root --> Hash 0--> Hash 0-0,可以快速定位到發生改變的L1。

2.1.5 MPT狀態樹

Patricia樹,如圖2-8所示,是一種更節省空間的壓縮前綴樹。對于基數樹的每個節點,如果該節點是唯一的兒子的話,就和父節點合并。

圖2-8 Patricia樹

MPT(Merkle Patricia Tree)見名知意,就是這兩者混合后的產物,在以太坊(Ethereum)中,它使用了一種特殊的十六進制前綴(hex-prefix, HP)編碼,用來對key進行編碼。所以在字母表中就有16個字符。每個節點可能有16個孩子,這其中的一個字符為一個nibble(半個字節,4位)。

一個nibble被加到key前(圖2-9中的prefix),對終止符的狀態和奇偶性進行編碼。最低位表示奇偶性,第二低位編碼終止符狀態。如果key是偶數長度,那么加上另外一個nibble,值為0來保持整體的偶特性。

圖2-9 以太坊狀態MPT樹

MPT樹中的節點包括空節點、葉子節點、擴展節點和分支節點:

?空節點,簡單表示空,在代碼中是一個空串。

?葉子節點(leaf),表示為 [key, value]的一個鍵值對,其中key是key的一種特殊十六進制編碼,value是value的RLP編碼。

?分支節點(branch),因為MPT樹中的key被編碼成一種特殊的16進制的表示,再加上最后的value,所以分支節點是一個長度為17的list,前16個元素對應著key中的16個可能的十六進制字符,如果有一個[key, value]鍵值對在這個分支節點終止,最后一個元素代表一個值,即分支節點既可以是路徑的終止也可以是路徑的中間節點。

?擴展節點(extension),也是 [key, value]的一個鍵值對,但是這里的value是其他節點的Hash值,這個Hash可以被用來查詢數據庫中的節點。也就是說通過Hash鏈接到其他節點。

如圖2-9所示,總共有2個擴展節點,2個分支節點,4個葉子節點。

其中葉子節點的鍵值情況如圖2-10所示。

圖2-10 葉子節點鍵值對

節點的前綴,如圖2-11所示。

圖2-11 前綴定義節點類型

簡單來說,MPT樹的特點如下:

?葉子節點和分支節點可以保存value,擴展節點保存key;

?沒有公共的key就成為2個葉子節點;

?有公共的key則根據公共前綴提取為一個擴展節點;這個擴展節點的key為所有數據在公共前綴之外拼接字節序列,value為子節點的Hash;

?如果有節點的key和公共的key一致,則該數據保存到下一級的分支節點中。

在以太坊中,MPT數據和驗證被廣泛應用。在區塊鏈中,區塊block打包了大量的交易和合約及賬號的狀態,如何保證每個區塊的這些交易是可以被驗證以及狀態被頻繁的改變呢?實際在區塊結構中包含區塊頭header, header里面包含3種類型的樹根,如圖2-12所示。

            // Header represents a block header in the Ethereum blockchain.
        type Header struct {
            ParentHash  common.Hash     `json:"parentHash"        gencodec:"required"`
            UncleHash    common.Hash     `json:"sha3Uncles"        gencodec:"required"`
            Coinbase     common.Address `json:"miner"              gencodec:"required"`
            Root          common.Hash     `json:"stateRoot"          gencodec:"required"`
            TxHash       common.Hash     `json:"transactionsRoot" gencodec:"required"`
            ReceiptHash common.Hash     `json:"receiptsRoot"      gencodec:"required"`

圖2-12 以太坊區塊頭結構

1.狀態樹:stateRoot

狀態樹是全局的樹:

?path = sha3(ethereumAddress):以太坊賬戶地址。

?value = rlp([nonce, balance, storageRoot, codeHash]):交易次數、賬戶余額、存儲樹、合約代碼Hash。

其中storageRoot是另一個trie樹,存儲合約的所有數據,每個賬戶都有一個獨立的storageRoot樹。

2.交易樹:transactionsRoot

每個block都有一個交易樹。

?path = rlp(transactionIndex):該交易在block中的索引,順序由礦工決定。

?value = 交易記錄。

該樹生成后永遠不會被修改。

3.收據樹:receiptsRoot

每個block都有一個收據樹。

?path = rlp(receiptIndex):該交易在block中的生成receipt的索引,順序由礦工決定。

?value = receipt記錄。

該樹生成后永遠不會被修改。

主站蜘蛛池模板: 留坝县| 兖州市| 南郑县| 贵阳市| 安远县| 怀来县| 梁平县| 玉山县| 桑植县| 秦皇岛市| 洛浦县| 广元市| 宣城市| 江油市| 岚皋县| 宝应县| 仲巴县| 昔阳县| 新兴县| 扶余县| 布尔津县| 淮阳县| 临泽县| 丹东市| 资源县| 德庆县| 泾阳县| 沾益县| 灵宝市| 永昌县| 广昌县| 肃宁县| 常宁市| 苗栗县| 保山市| 建水县| 九龙县| 大理市| 台前县| 龙南县| 方山县|