從區塊鏈到 NFT 到元宇宙

toBeTheLight發表於2022-03-20

以比特幣白皮書來說,區塊鏈的本質是一個 P2P(點對點)的分散式賬本,節點發出交易時需要向周圍節點廣播,全節點(還有輕節點等,輕節點一般只儲存和自己有關的資料)將收到的交易資訊寫入區塊體中,在共識機制(PoW 即挖礦,還有其他如 PoS、DPoS 機制)作用下獲取到記賬的許可權(即認證某些交易資料,向區塊鏈中寫入區塊的許可權),將新區塊廣播至周圍節點,其它節點校驗資料有效性和記賬許可權後根據區塊資料同步本地狀態,從而達成整個區塊網路的狀態一致,同時由於新區塊頭中有前一區塊的關聯資訊,從而使區塊之間達成連線,形成區塊鏈。

簡單來說,就是網路中所有結點做完全相同的操作在本地維護一個狀態一致性的分散式系統。

  • 區塊鏈結構:區塊 + 雜湊連結

    • 鏈:新的區塊通過區塊間雜湊(雜湊演算法是一種固定輸入產生固定輸出的演算法,可以理解為對輸入產生了一個身份ID)關聯,區塊間通過這種關聯成鏈
    • 區塊:

      • 區塊體:

        • 區塊體的結構往往是一顆樹
        • 樹的葉子結點儲存實際資料,即賬本資訊(如交易資訊)等
        • 兩兩向上逐層求得雜湊值形成雜湊樹(即 Merkle Tree 或使用其變種結構)
      • 區塊頭:負責基本功能,一般包含以下部分

        • 對前一個區塊的雜湊求值(僅對前一區塊的區塊頭的內容進行雜湊運算)
        • 雜湊樹的根雜湊
        • 時間戳

  • 防篡改:

    • 單個塊:當單個塊中的交易資訊被某個結點修改後,勢必會逐層向上影響至儲存在區塊頭中的雜湊樹的根雜湊,那麼就需要修改根雜湊才能達成單個區塊資料篡改有效性
    • 鏈:如果某個區塊中的根雜湊被修改,那麼對這個區塊的區塊頭進行雜湊運算必然會產生新值,和它的下一個區塊中的對它的雜湊引用的數值對不上,則必然會影響到後續整個鏈上的所有區塊,所以需要修改後續的所有區塊才能達成篡改的有效性
    • 這種對整個區塊鏈篡改的可行性需要破壞已形成的共識,需要有壓制性的優勢,理論上在參與者眾多的系統中很難達成,從而保證了區塊鏈的防篡改特徵

比特幣

狀態

比特幣的節點在本地維護了一個賬本叫做 UTXO(Unspent Transaction Output)的狀態,即未使用的交易輸出,可以簡單的理解為某筆未使用的轉賬輸入,用於進行後續轉賬的轉出使用。

執行機制

比特幣區塊的區塊體中以雜湊樹的結構儲存了新發生的每筆交易。

單筆交易的一種協議資訊的示例如下:

In: 
// 表示這次交易使用的資金的來源
    Previous tx: f5d8...430901c91
    // 資金來源所在交易的雜湊
    Index: 0
    // 資金來源交易雜湊中的哪個轉出
    scriptSig: 3045...798a4 618c...41501
    // 對宣告的資金來源的解鎖指令碼,含有簽名和公鑰
    // 可以簡單理解為銀行卡號和密碼

Out: // 表明這次交易的輸出
    Value: 5000000000
    // 金額
    scriptPubKey: OP_DUP OP_HASH160 4043...549d OP_EQUALVERIFY OP_CHECKSIG
    // 鎖定指令碼,含有對公鑰的雜湊和一些運算子,使用這筆轉出的交易需要在它的 scriptSig 提供可以通過指令碼的資訊才能解鎖
    // 可以理解為銀行卡號的雜湊和一些操作指令
  • 非對稱加密概念科普:

    • 公鑰、私鑰是是成對生成的
    • 公鑰公開給系統中的其他節點使用,私鑰個人保留
    • 錢包地址一般是使用公鑰進行一系列運算得來的
    • 我們不明確區分錢包地址和公鑰,可不準確的理解為

      • 公鑰、錢包:賬戶賬號
      • 私鑰:賬戶密碼
    • 公鑰私鑰還有一密碼學特徵:單一鑰匙進行加密的資料必定且只能用另一鑰匙進行解密

      • 加密:傳送者使用接受者的公鑰對內容加密,只有接受者才能用自己的私鑰對內容解密,避免了傳送路徑上的資料洩露
      • 簽名驗證:更進一步,發出者使用自己的私鑰對內容進行簽名,將簽名和內容發給接受者,接受者使用傳送者的公鑰對簽名解密,發現簽名和內容一致,說明傳送路徑上,內容未被篡改
  • 交易資訊:每筆交易以之前的交易記錄為基礎,需要滿足輸入等於輸出(其實是存在差額的,差額會作為節點的工作費)

    • 示例:A 賬號要支付 6 比特幣給 D 賬號,則交易資料寫為:

      • 輸入:

        • 之前某筆交易的輸出( B 賬號支付 2 比特幣給 A 賬號)
        • 之前某筆交易的輸出( C 賬號支付 3 比特幣給 A 賬號)
        • 之前某筆交易的輸出( D 賬號支付 2 比特幣給 A 賬號)
      • 輸出:

        • 支付 6 比特幣給 D 賬號
        • 支付 1 比特幣給 A 賬號
    • 特點:

      • 需要說明支出的來源
      • 說明的資金來源中的金額不能拆分,多出的部分可以通過找零的方式轉給自己
  • 交易資訊進鏈:

    • 分散式:

      • 網路上很多節點進行儲存和資料認證
      • 傳送節點向周圍節點廣播交易資訊
      • 每個節點都儲存了區塊鏈的完整或部分資料
    • 有效性:如某賬號同時傳送了多條矛盾的交易資訊

      • 全節點將收到的交易資訊進行驗證並寫入區塊
      • 驗證過程會使用到交易資訊中的指令碼

        • 節點發出的交易資訊包含

          • 對資金來源的說明
          • 對每筆資金來源的解鎖資訊
        • 接收到資訊的節點:

          • 確認 UTXO:驗證資金來源是否存在 UTXO 中,即是否未使用
          • 執行指令碼:

            • 將解鎖簽名和對應交易輸出包含的鎖定指令碼拼接後以棧結構執行
            • 以示例資訊為例子,拼接為 [私鑰簽名, 公鑰](解鎖指令碼提供)[OP_DUP, OP_HASH160, 公鑰雜湊, OP_EQUALVERIFY, OP_CHECKSIG](鎖定指令碼提供)
            • 執行大致為:

              • 將公鑰取出(OP_DUP)
              • 進行雜湊 160 運算(OP_HASH160)
              • 將結果與公鑰雜湊對比是否相等(OP_EQUALVERIFY)
              • 驗證簽名(OP_CHECKSIG)
          • 確權:如何確認 UTXO 的所有權

            • 指令碼執行過程中的將解鎖指令碼中公鑰雜湊後與鎖定指令碼中公鑰雜湊對比的過程即是確定所有所有權的操作
            • 確認了提供的銀行卡賬號確實是有這筆餘額的賬號
          • 交易資訊防偽:

            • 傳送節點:

              • 對交易資訊雜湊獲得一個摘要
              • 用傳送節點的私鑰對摘要加密生成簽名
              • 將內容和簽名進行廣播
            • 接收節點:執行 OP_CHECKSIG 的過程

              • 使用傳送節點的公鑰對簽名解密得到摘要 1
              • 對內容進行雜湊,得到摘要 2
              • 摘要 1 等於摘要 2 則公鑰、私鑰匹配,內容可信
              • 即交易資訊是解鎖這個交易輸出的公鑰的對應私鑰擁有者寫的
    • 一致性:網路傳輸問題,每個節點收到的交易資訊可能不同,那麼他們驗證並寫入區塊的交易可能不同,如果都要上鍊則會造成最終生成的區塊資訊不一致

      • PoW:使用共識規則讓單位時間內(約10分鐘)只有一個節點有合法釋出區塊的權力,即獲得了記賬權
      • 規則:

        1. 將新區塊的內容(前一個區塊頭雜湊、這個區塊的基本資訊、新區塊的交易資訊根雜湊)組合成字串
        2. 在新區塊字串的末尾加上一個隨機數,進行SHA-256,如果結果的二進位制前 72 位全是 0 則工作完成,這是一個工作量巨大的碰運氣工作
        3. 隨著整體算力的提高會提升難度(增加 0 的數量等)將出塊時間穩定在 10 分鐘
      • 這個過程又因此被稱為挖礦,需要大量的算力,進行 PoW 的節點又稱為礦工
      • 節點收入:

        • 比特幣網路獎勵:節點在出塊的時候會寫入對自己地址進行 6.25 個比特幣輸出的獎勵交易(Coinbase Newly Generated Coins)資訊,此獎勵每過 21 萬個區塊減半,也是比特幣的發幣方式
        • 區塊中的交易手續費:區塊中所有輸入和輸出金額的差值,由礦工寫給自己
    • 分叉:湊巧同時生成了兩個區塊,則兩個塊都會並列入鏈,形成臨時分叉,在後續計算中採用最長鏈原則,廢棄追加區塊數量少的分叉
    • 交易確認:正是因為分叉和廢塊的存在,所以交易一般需要在繼續追加多個區塊後才會被確認,比特幣一般需要經過 6 個區塊的追加才算是相對安全
    • 篡改:需要有超高的算力,篡改者的算力需要超過整個網路的算力才能實現篡改,才會使得自己的篡改分叉變成最長鏈,完成篡改

總結

  1. 使用 SHA-256 雜湊演算法和非對稱加密製作數字簽名進行防偽
  2. 執行交易資訊中的指令碼驗證驗證簽名
  3. 使用區塊鏈的區塊儲存比特幣交易資訊,使用雜湊樹儲存並防止交易資料篡改
  4. 利用額外的工作規則達成網路一致性(拼算力)
  5. 將網路獎勵和交易手續費支付給礦工節點

以太坊

以太坊黃皮書

https://ethereum.github.io/ye...

以太坊同樣是一個交易驅動的狀態機,它支援智慧合約,是可程式設計的區塊鏈,一種去中心化應用的平臺。

我們看到比特幣有一定的指令碼執行能力,但是其指令碼比較簡單,只支援幾種固定的交易協議和指令碼命令。以太坊則不同,其是圖靈完備的,區塊鏈開發者可在支援範圍內自由程式設計。

結構

區塊頭

  • parentHash:前一區塊區塊頭計算雜湊
  • ommersHash:叔塊雜湊
  • beneficiary:出塊獎勵的賬戶地址
  • stateRoot:狀態樹根雜湊
  • transactionsRoot:交易樹根雜湊
  • receiptsRoot:收據樹根雜湊
  • logsBloom:資料布隆過濾器
  • gasLimit:區塊汽油費上限,由礦工們共同維護

ommersHash

由於以太坊的出塊速度較快,它出現臨時分叉的可能性也就越高,ommersHash 正是出於抑制分叉的目的設計的,思路如下,被生效區塊寫入 ommersHash (最多寫入兩個)的廢塊也可以獲得出塊獎勵,第一級是 7/8,第二級是 6/8,生效區塊寫入一個廢塊也會有 1/32 的獎勵。

布隆過濾器

布隆過濾器是一種比較高效但是不太準的查詢演算法,大致機制如下:

  1. 提供一批雜湊函式
  2. 將輸入對映到陣列的幾個點上
  3. 查詢時查詢陣列對應的點是否都有值即可

如:

  • 存:

    • apple 對映到點 1、7、9
    • banana 對映到點 1、3、7
  • 查:

    • orange 對映值為 1、3、9,都有值,則 orange 可能存在
    • peach 對映值為 2、3、7,2 無值,則 peach 一定不存在

可以看到布隆過濾器對存在的判斷是會誤報的,但是不存在的一定不會誤報,所以適合大資料量的過濾。

區塊體

與比特幣不同的是,以太坊的區塊體中有三棵樹:狀態樹、交易樹、收據樹,樹的資料會被全節點維護在本地資料庫中。

  • 狀態樹:

    • 一種變種壓縮字首雜湊樹(Merkle Patricia Tree,MPT)
    • 儲存了所有已知(即發生過交易的)的賬戶的狀態

      • 外部賬戶:即錢包地址,無程式碼,能轉賬或執行智慧合約
      • 合約賬戶:建立合約時生成,存有程式碼,能被觸發執行智慧合約
      • 賬戶狀態:

        • nonce:地址的交易數量或合約數量
        • balance:餘額
        • codeHash:合約賬戶的程式碼的雜湊,外部賬戶沒有
        • storageRoot:賬戶內容( 也是一棵 MPT 樹)的根雜湊
    • 新區塊只會實際儲存發生變化的賬戶的新狀態,未發生變化的會指向之前的區塊狀態樹的對應位置
  • 交易樹:驅動狀態樹發生變更,也是 MPT,只儲存區塊中發生的交易資訊

    • 訊息呼叫的資訊
    • 合約賬戶建立的資訊
  • 收據樹:也是 MPT

    • 交易執行過程中的特定資訊編碼為交易資料,儲存在一個索引為鍵的樹中
    • 交易過程中建立的日誌會構成區塊頭的 Bloom 過濾器

共識機制

目前以太坊也是使用 PoW (工作量證明)即挖礦的方式確定記賬權。

由一個 seed 生成一個小資料集(MB 級別),再由小的資料集生成一個大的資料集(GB 級別大小),在使用區塊頭和一個 nonce 值求取雜湊,對映至大資料集,讀取大資料集中目標位置和相鄰元素,再雜湊,迴圈 64 次,算出一個雜湊和挖礦目標值對比。失敗則更換區塊頭中 nonce 重新計算。

同時以太坊也有像 PoS 機制轉換的計劃,在以太坊的難度調節機制中存在一個難度因子,此難度因子每 10萬個區塊會翻一倍,是一個指數級的因子,所以又稱為難度炸彈,由於 PoS 機制的上線不順利,而難度炸彈導致出塊速度提升過快,所以以太坊修改了其程式碼,使得難度因子計算時減去了 300 萬個區塊,也是由於程式碼升級導致了以太坊網路的一次硬分叉,還好這次分叉網路節點都進行了程式碼升級。

智慧合約

前面提到,以太坊網路中存在合約賬戶,賬戶記憶體有一段程式碼,所以智慧合約就是按照既定邏輯執行的程式碼。

呼叫

外部賬戶呼叫合約賬戶,外部賬戶發起的合約賬戶呼叫也可以呼叫另一個合約賬戶。

外部賬戶呼叫時,將目標函式和引數寫在 data 域中,向合約賬戶發起交易。

合約呼叫合約的一種方式:

contract A {
    event LogCallFoo (string str);
    function foo (string str) returns (uint) {
        emit LogCallFoo(str);
        return 123;
    }
}

contract B {
    uint ua; // callAFooDirectly 的執行返回值
    function callAFooDirectly (address addr) public {
        A a = A(addr);
        ua = a.foo("call foo directly); // 呼叫 a 地址的 foo 方法
    }
}

建立和執行

  • 智慧合約程式碼編寫(solidity 等)完之後,編譯成 bytecode
  • 建立:外部賬戶向 0*0 地址發起交易,將程式碼放在 data 域中
  • 智慧合約執行在 EVM 中
  • 呼叫智慧智慧合約的交易將函式和引數寫在交易的 data 域中,釋出到區塊鏈上後,受到訊息的礦工則會按照引數執行程式碼
  • 發起呼叫的賬戶需要支付汽油費 GasLimit

    • 執行前全額扣除,開始執行,多退少補,不夠則狀態回滾,汽油費不退
    • 執行出錯則整體狀態回滾,汽油費不退:一個不嚴謹的例子,智慧合約能發起了向 A、B 的轉賬,如果向 B 的轉賬出錯了,向 A 轉賬的狀態也會回滾
  • 每個交易執行之後形成一個收據,存有執行結果等資訊

一個例子

這是一個拍賣出價的智慧合約:

contract SimpleAuctionV1 {
    address public beneficiary; // 受益人
    uint public auctionEnd; // 結束時間
    address public highestBidder; // 當前最高出價地址
    mapping(address => unit) bids; // 所有出價
    address[] bidders; // 所有出價地址
    bool ended; // 是否結束

    event HighestBidIncreased(address bidder, uint amount);
    event AuctionEnded(address winner, unit amount);

    constructor(uint _biddingTime, address _beneficiary) public {
        beneficiary = _beneficiary;
        auctionEnd = now + _biddingTime
    }
}

// 參與拍賣的地址向此合約地址發起交易並支付貨幣
function bid() public payable {
    require(now <= auctionEnd);
    require(bids[msg.sender]+msg.value > bids[highestBidder]);
    // 沒出過價則把出價人存起來
    if (!(bids[msg.sender] == unit(0))) {
        bidders.push(msg.sender);
    }
    // 如果出價最高則修改當前最高出價人
    highestBidder = msg.sender;
    bids[msg.sender] += msg.value;
    emit HighestBidIncreased(msg.sender, bins[msg.sender]);
}
// 拍賣結束
function auctionEnd()public {
    require(now > auctionEnd);
    require(!ended);
    // 把最高出價轉給受益人
    beneficiary.transfer(bids[highestBidder]);
    // 給沒競拍成功的人退錢
    for (uint i = 0; i< bidders.length; i++) {
        address bidder = bidders[i];
        if (bidder == highestBidder) continue;
        bidder.transfer(bids[bidder]);
    }

    ended = true;
    emit AuctionEnded(highestBidder, bids[highestBidder]);
}
  • 這裡存在一個問題:beneficiary.transfer,如果 beneficiary 是一個無法接受支付的合約地址(未宣告 payable 關鍵詞),那麼會導致 auctionEnd 執行失敗,導致所有出價鎖在智慧合約地址中,永遠無法取出
  • 除此之外還有重入攻擊,即呼叫的合約賬戶可能會反過來呼叫,引發迴圈執行
Code Is Law。

智慧合約的邏輯由程式碼決定,已釋出的合約程式碼無法修改,如果釋出新的程式碼則會生成另一個合約賬戶地址,所以就算有 Bug 也無法修改。

The DAO(Decentralized Autonomous Organization):利用了重入攻擊,黑客在自己的合約賬戶得收款函式中寫了向 The DAO 發起呼叫的程式碼,使用迴圈呼叫的方式,轉走了 5000萬/1.5億美元的以太幣,約 10% 的以太坊系統總量的以太幣,為了彌補對以太坊穩定性的重大影響,經過軟分叉修復失敗,以太幣質押投票,以太坊選擇了硬分叉的方式,將 The DAO 中的以太幣強行轉出,造成了社群分裂,造成以太坊硬分叉為 ETH、ETC。

NFT

從智慧合約的例子中也可以看到,智慧合約的開發者可以在合約賬戶的內部維護一個狀態樹。

其中的一種應用方式就是基於智慧合約在區塊鏈的網路內發行 Token,將所有持有人的狀態維護在合約賬戶中。

NFT 正是這樣的一種應用,全稱為非同質化代幣。NFT 的一大特徵是,基於區塊鏈的特性公開且防篡改的確定了某地址對某個數字作品的所有權。

一個 ERC721 標準(一種以太坊上發行 NFT 的標準)的 NFT 在區塊鏈的資料示例:

由於 https 的內容會被修改,tokenURI 一般會用 IPFS 代替(分散式檔案儲存系統)。這個示例表明某地址擁有編號為 6 的某個物品,其內容為 tokenURI 的內容。

大多數 NFT 的實現上看,在區塊鏈上只是記錄了一個作品擁有者的地址、作品的編號、以及這個作品的連結,而作品本身在鏈下。所以也出現過現實中藝術家的作品被他人拿走鑄造成 NFT 出售的情況,所以 NFT 不能解決線下的問題。從理論上講,NFT 會提供數字資產的流通性,但是不能給物品本身賦予價值

通過呼叫智慧合約的函式,可以進行 NFT 的轉移、交易。所以可以知道,NFT 不單指某一個代幣,而是一種代幣型別,你也可以通過建立自己的智慧合約,發行自己的 NFT 或 NFT 平臺。

元宇宙

區塊鏈應該會是元宇宙實現的一個技術,作為可任意複製的數字世界的數字資產進行確權的基礎,如現有的 Decentraland 的虛擬土地、創造品的售賣。

同時,對於在數字世界中誕生的虛擬物品,由於不需要解決線下問題,NFT 的應用性應該會更高一些。

其他

  • 空塊:在部分割槽塊鏈中,新區塊產生後,會先廣播區塊頭再廣播區塊體,由於區塊體資料較大,下載需要時間,部分礦工節點會在收到區塊頭後就開始挖礦,由於向新區塊體中寫入的交易資訊不能與已有區塊衝突或重複,所以為保證在未拿到前一區塊資訊的情況下的新區塊的有效性,不向新區塊體中寫入實際交易資訊(獎勵資訊除外),從而產生空塊。

總結

  • 區塊鏈存在交易慢、耗能高詬病,但要結合場景觀測,在具體的場景下是比現有的解決方案快的。
  • 從理論上講,基於區塊鏈的 NFT 數字資產交易會提供數字資產的流通性,但是不能給物品本身賦予價值。
  • 投資有風險,追逐熱點要謹慎。

參考資料

相關文章