構建第一個 Swift 區塊鏈應用
區塊鏈作為一項革命性的技術,開始受到越來越多追捧。為什么呢?因為區塊鏈是許多加密數字貨幣的底層技術,比如:比特幣(BTC),以太坊(ETH)以及萊特幣(LTC)。區塊鏈具體是如何工作的?本篇教程會涵蓋所有區塊鏈相關的知識,還會教你如何構建 Swift 區塊鏈。下面讓我們開始吧!
區塊鏈的工作原理
顧名思義,區塊鏈是一條由不同區塊連接組成的鏈。每一個塊包含三個信息:數據、哈希(hash)、以及前置區塊的哈希。
- 數據 – 由于應用場景不同,存儲在區塊中的數據由區塊鏈的類型決定。例如,在比特幣區塊鏈中,存儲的數據是交易信息:轉賬金額和交易雙方的信息。
- 哈希 – 你可以將哈希看做數字指紋,用來唯一標識一個區塊及其數據。哈希的重要之處在于它是一個獨特的字母數字代碼,通常是 64 個字符。當一個區塊被創建時,哈希也隨之創建。當一個區塊被修改,哈希也隨之修改。因此,當你想要查看在區塊上所做的任何變更時,哈希就顯得非常重要。
- 前置區塊的哈希 – 通過存儲前置區塊的哈希,你可以還原每個區塊連接成區塊鏈的過程!這使得區塊鏈安全性特別高。
我們來看下這張圖片:

區塊鏈
你可以看到,每一個區塊包含數據(圖片中沒有指明)、哈希以及前置區塊的哈希。例如,黃色區塊包含自身的哈希:H7s6,以及紅色區塊的哈希:8SD9。這樣它們就構成了一條相互連接的鏈。現在,假如有一個黑客準備惡意篡改紅色的區塊。請記住,每當塊以任何方式被篡改時,該區塊的哈希都會改變!當下一個區塊檢查并發現前置哈希不一致時,黑客將無法訪問它,因為他與前置區塊的聯系被切斷了(譯者注:即如果黑客想要要篡改一個區塊的話,就需要把這個區塊后面的所有區塊都要改掉,而這個工作量是很難實現的)。
這使得區塊鏈特別安全,幾乎不可能回滾或者篡改任何數據。雖然哈希為保密和隱私提供了巨大的保障,但是還有兩個更加安全妥當的措施讓區塊鏈更加安全:工作量證明(Proof-of-Work)以及智能合約(Smart Contracts)。本文我不會深入講解,你可以在這里了解更多相關知識。
區塊鏈最后一個保證自身安全性的方式是基于其定位。和大多數存儲在服務器和數據庫的數據不同,區塊鏈使用的是點對點(P2P)網絡。P2P 是一種允許任何人加入的網絡,并且該網絡上的數據會分發給每一個接收者。
每當有人加入這個網絡,他們就會獲得一份區塊鏈的完整拷貝。每當有人新建一個區塊,就會廣播給全網。在將該塊添加到鏈之前,節點會通過幾個復雜的程序確定該塊是否被篡改。這樣,所有人、所有地方都可以使用這個信息。如果你是 HBO 美劇硅谷 的粉絲,對此應該不會感到陌生。在該劇中,主演(Richard)使用一種相似的技術創造了新型互聯網(譯者注:有趣的是劇中還發行了區塊鏈數字貨幣 PiedPaperCoin,感興趣的童鞋可以刷一下這部劇)。
因為每個人都有區塊鏈或者節點的一份拷貝,他們可以達成一種共識并決定哪部分區塊是有效的。因此,如果你想要攻擊某個區塊,你必須同時攻擊網絡上 50% 以上的區塊(譯者:51% 攻擊),使得你的區塊可以追上并替換原區塊鏈。所以區塊鏈或許是過去十年所創造的最安全的技術之一。
關于示例程序
現在你已經對區塊鏈的原理有了初步的認識,那么我們就開始寫示例程序吧!你可以在這里下載原始項目。
如你所見,我們有兩個比特幣錢包。第一個賬戶 1065 有 500 BTC,而第二個賬戶 0217 沒有 BTC。我們通過 send 按鈕可以發送比特幣到另外的賬戶。為了賺取 BTC,我們可以點擊 Mine 按鈕,可以獲得 50 BTC 的獎勵。我們主要工作是查看控制臺輸出,觀察兩個賬戶間的交易過程。

這里寫圖片描述
在左側導航欄可以看到兩個很重要的類:Block 和 Blockchain。目前這兩個類都是空實現,我會帶著你們在這兩個類中寫入相關邏輯。下面讓我們開始吧!

這里寫圖片描述
在 Swift 中定義區塊
首先打開 Block.swift 并添加定義區塊的代碼。在此之前,我們需要定義區塊是什么。前面我們曾定義過,區塊是由三部分組成:哈希、實際記錄的數據以及前置區塊的哈希。當我們想要構建我們的區塊鏈時,我們必須知道該區塊是第一個還是第二個。我們可以很容易地在 Swift 的類中做如下定義:
- var hash: String!
- var data: String!
- var previousHash: String!
- var index: Int!
現在需要添加最重要的代碼。我曾提過區塊在被修改的情況下,哈希也會隨之變化,這是區塊鏈如此安全的特性之一。因此我們需要創建一個函數去生成哈希,該哈希由隨機字母和數字組成。這個函數只需要幾行代碼:
- func generateHash() -> String {
- return NSUUID().uuidString.replacingOccurrences(of: "-", with: "")
- }
NSUUID 是一個代表通用唯一值的對象,并且可以橋接成 UUID。它可以快速地生成 32 個字符串。本函數生成一個 UUID,刪除其中的連接符,然后返回一個 String,最后將結果作為區塊的哈希。Block.swift 現在就像下面:

這里寫圖片描述
現在我們已經定義好了 Block 類,下面來定義 Blockchain 類,首先切換到 Blockchain.swift 。
在 Swift 中定義區塊鏈
和之前一樣,首先分析區塊鏈的基本原理。用非常基礎的術語來說,區塊鏈只是由一連串的區塊連接而成,也可以說是一個由多個條目組成的列表。這是不是聽起來很熟悉呢?其實這就是數組的定義!而且這個數組是由區塊組成的!接下來添加以下代碼:
- var chain = [Block]()
快速提示: 這個方法可以應用于計算機科學世界里的任何事物。如果你遇到大難題,可以嘗試把它分解成若干個小問題,以此來建立起解決問題的方法,正如我們解決在 Swift 中如何添加區塊和區塊鏈的問題。
你會注意到數組里面是我們前面定義的 Block 類,這就是區塊鏈所需要的所有變量。為了完成功能,我們還需要在類中添加兩個函數。請嘗試著根據我之前教過的方法解答這個問題。
區塊鏈中的兩個主要函數是什么?
我希望你能認真思考并回答這個問題!實際上,區塊鏈的兩個主要功能是創建創世區塊(最初的始塊),以及在其結尾添加新的區塊。當然現在我不打算實現分叉區塊鏈和添加智能合約的功能,但你必須了解這兩個是基本功能!將以下代碼添加到 Blockchain.swift:
- func createGenesisBlock(data:String) {
- let genesisBlock = Block()
- genesisBlock.hash = genesisBlock.generateHash()
- genesisBlock.data = data
- genesisBlock.previousHash = "0000"
- genesisBlock.index = 0
- chain.append(genesisBlock)
- }
- func createBlock(data:String) {
- let newBlock = Block()
- newBlock.hash = newBlock.generateHash()
- newBlock.data = data
- newBlock.previousHash = chain[chain.count-1].hash
- newBlock.index = chain.count
- chain.append(newBlock)
- }
1、我們添加的第一個函數的作用是創建創世區塊。為此,我們創建了一個以區塊數據為入參的函數。然后定義了一個類型為 Block 的變量 genesisBlock,它擁有此前在 Block.swift 中定義的所有變量和函數。我們將 generateHash() 賦值給哈希,將輸入的 data 參數賦值給數據。由于這是第一個區塊,我們將前置區塊的哈希設為 0000,這樣我們就可以知道這是起始區塊。最后我們將 index 設為 0,并將這個區塊加入到區塊鏈中。
2、我們創建的第二個函數適用于 genesisBlock 之后的所有區塊,并且能創建剩余的區塊。你會注意到它與第一個函數非常相似。唯一的區別是,我們將 previousHash 的值設置為前一個區塊的哈希值,并將 index 設置為它在區塊鏈中的位置。就這樣,區塊鏈已經定義好了!你的代碼應該看起來跟下圖一樣!

這里寫圖片描述
錢包后端
現在切換到 ViewController.swift,我們會發現所有的 outlet 都已經連接好了。我們只需要處理交易,并將其輸出到控制臺。
然而在此之前,我們需要稍微研究一下比特幣的區塊鏈。比特幣是由一個總賬戶產生的,我們將這個賬號的編號稱為 0000。當你挖到一個 BTC,意味著你解決了數學問題,因此會發行一定數量的比特幣作為獎勵。這提供了一個發幣的高明方法,并且可以激勵更多人去挖礦。在我們的應用,讓我們把挖礦獎勵設為 100 BTC。首先,在視圖控制器中添加所需的變量:
- let firstAccount = 1065
- let secondAccount = 0217
- let bitcoinChain = Blockchain()
- let reward = 100
- var accounts: [String: Int] = ["0000": 10000000]
- let invalidAlert = UIAlertController(title: "Invalid Transaction", message: "Please check the details of your transaction as we were unable to process this.", preferredStyle: .alert)
首先定義號碼為 1065 和 0217 的兩個賬號。然后添加一個名為 bitcoinChain 的變量作為我們的區塊鏈,并將 reward 設為 100。我們需要一個主帳戶作為所有比特幣的來源:即創世帳戶 0000。里面有 1000 萬個比特幣。你可以把這個賬戶想象成一個銀行,所有因獎勵產生的 100 個比特幣都經此發放到合法賬戶中。我們還定義了一個提醒彈窗,每當交易無法完成時就會彈出。
現在,讓我們來編寫幾個運行時需要的通用函數。你能猜出是什么函數嗎?
- 第一個函數是用來處理交易的。我們需要確保交易雙方的賬戶,能夠接收或扣除正確的金額,并將這些信息記錄到我們的區塊鏈中。
- 下一個函數是在控制臺中打印整個記錄 —— 它將顯示每個區塊及其中的數據。
- 最后一個是用于驗證區塊鏈是否有效的函數,通過校驗下一個區塊的 previousHash 和上一個區塊 hash 是否匹配。由于我們不會演示任何黑客方法,因此在我們的示例程序中,區塊鏈是永遠有效的。
交易函數
下面是一個通用的交易函數,請在我們定義的變量下方輸入以下代碼:
- func transaction(from: String, to: String, amount: Int, type: String) {
- // 1
- if accounts[from] == nil {
- self.present(invalidAlert, animated: true, completion: nil)
- return
- } else if accounts[from]!-amount < 0 {
- self.present(invalidAlert, animated: true, completion: nil)
- return
- } else {
- accounts.updateValue(accounts[from]!-amount, forKey: from)
- }
- // 2
- if accounts[to] == nil {
- accounts.updateValue(amount, forKey: to)
- } else {
- accounts.updateValue(accounts[to]!+amount, forKey: to)
- }
- // 3
- if type == "genesis" {
- bitcoinChain.createGenesisBlock(data: "From: \(from); To: \(to); Amount: \(amount)BTC")
- } else if type == "normal" {
- bitcoinChain.createBlock(data: "From: \(from); To: \(to); Amount: \(amount)BTC")
- }
- }
代碼量看起來好像很大,但主要是定義了每個交易需要遵循的一些規則。一開始是函數的四個參數:
to,from,amount,type。前三個參數不需要再解釋了,而 Type 主要用于定義交易的類型。總共有兩個類型:正常類型(normal) 和創世類型(genesis)。正常類型的交易會發生在賬戶 1065 和 2017 之間,而創世類型將會涉及到賬戶 0000。
- 第一個 if-else 條件語句處理轉出賬戶的信息。如果賬戶不存在或者余額不足,將會提示交易不合法并返回。
- 第二個 if-else 條件語句處理轉入賬戶的信息。如果賬戶不存在,則創建新賬戶并轉入相應的比特幣。反之,則向該賬戶轉入正確數量的比特幣。
- 最后一個 if-else 條件語句處理交易類型。如果類型是創世類型,則添加一個創世區塊,否則創建一個新的區塊存儲數據。
打印函數
為了確保交易正確執行,在每個交易結束后,我們希望拿到所有交易的清單。以下是我們在交易函數下方的代碼,用來打印相關信息:
- func chainState() {
- for i in 0...bitcoinChain.chain.count-1 {
- print("\tBlock: \(bitcoinChain.chain[i].index!)\n\tHash: \(bitcoinChain.chain[i].hash!)\n\tPreviousHash: \(bitcoinChain.chain[i].previousHash!)\n\tData: \(bitcoinChain.chain[i].data!)")
- }
- redLabel.text = "Balance: \(accounts[String(describing: firstAccount)]!) BTC"
- blueLabel.text = "Balance: \(accounts[String(describing: secondAccount)]!) BTC"
- print(accounts)
- print(chainValidity())
- }
這是一個簡單的循環語句,遍歷 bitcoinChain 中的所有區塊,并打印區塊號碼,哈希,前置哈希,以及存儲的數據。同時我們更新了界面中的標簽(label),這樣就可以顯示賬戶中正確的 BTC 數量。最后,打印所有的賬戶(應該是 3 個),并校驗區塊鏈的有效性。
現在你應該會在函數的最后一行發現一個錯誤。這是由于我們還沒有實現 chainValidity() 函數,讓我們馬上開始吧。
有效性函數
判斷一個鏈是否有效的標準是:前置區塊的哈希與當前區塊所表示的是否匹配。我們可以再次用循環語句來遍歷所有的區塊:
- func chainValidity() -> String {
- var isChainValid = true
- for i in 1...bitcoinChain.chain.count-1 {
- if bitcoinChain.chain[i].previousHash != bitcoinChain.chain[i-1].hash {
- isChainValid = false
- }
- }
- return "Chain is valid: \(isChainValid)\n"
- }
和之前一樣,我們遍歷了 bitcoinChain 中的所有區塊,并檢查了前置區塊的 hash 是否與當前區塊的 previousHash 一致。
就醬!我們已經將定義了所有需要的函數!你的 ViewController.swift 應該如下圖一樣:

這里寫圖片描述
收尾工作就是連接按鈕和函數啦。讓我們馬上開始最后的部分吧!
讓一切關聯起來
當我們的應用第一次啟動時,需要創世賬戶 0000 發送 50 BTC 到我們的第一個賬戶。再從第一個賬戶轉賬 10 BTC 到第二個賬戶,這只需要寥寥三行代碼。最后 viewDidLoad 中的代碼如下:
- override func viewDidLoad() {
- super.viewDidLoad()
- transaction(from: "0000", to: "\(firstAccount)", amount: 50, type: "genesis")
- transaction(from: "\(firstAccount)", to: "\(secondAccount)", amount: 10, type: "normal")
- chainState()
- self.invalidAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
- }
我們使用已定義好的函數轉賬,并調用 chainState() 函數。最后,我們還在 invalidAlert 中添加了一個標題為 OK 的 UIAlertAction。
現在讓我們來實現剩下的四個函數:ReMeNe()、BrimeMeNe()、ReSdEnter()和BuLeScript()。
挖礦函數
挖礦函數特別簡單,只需要三行代碼。添加以下代碼:
- @IBAction func redMine(_ sender: Any) {
- transaction(from: "0000", to: "\(firstAccount)", amount: 100, type: "normal")
- print("New block mined by: \(firstAccount)")
- chainState()
- }
- @IBAction func blueMine(_ sender: Any) {
- transaction(from: "0000", to: "\(secondAccount)", amount: 100, type: "normal")
- print("New block mined by: \(secondAccount)")
- chainState()
- }
在第一個挖礦函數中,我們使用交易函數從創世賬戶發送了 100 BTC 到第一個賬戶。我們打印了挖礦的區塊,然后打印了區塊鏈的狀態。同樣地,在 blueMine 函數中,我們轉給了第二個賬戶 100 BTC。
發送函數
發送函數和挖礦函數略微相似:
- @IBAction func redSend(_ sender: Any) {
- if redAmount.text == "" {
- present(invalidAlert, animated: true, completion: nil)
- } else {
- transaction(from: "\(firstAccount)", to: "\(secondAccount)", amount: Int(redAmount.text!)!, type: "normal")
- print("\(redAmount.text!) BTC sent from \(firstAccount) to \(secondAccount)")
- chainState()
- redAmount.text = ""
- }
- }
- @IBAction func blueSend(_ sender: Any) {
- if blueAmount.text == "" {
- present(invalidAlert, animated: true, completion: nil)
- } else {
- transaction(from: "\(secondAccount)", to: "\(firstAccount)", amount: Int(blueAmount.text!)!, type: "normal")
- print("\(blueAmount.text!) BTC sent from \(secondAccount) to \(firstAccount)")
- chainState()
- blueAmount.text = ""
- }
- }
首先,我們檢查 redAmount 或者 blueAmount 的文本值是否為空。如果為空,則彈出無效交易的提示框。如果不為空,則繼續下一步。我們使用交易函數從第一個賬戶轉賬到第二個賬戶(或者反向轉賬),金額為輸入的數量。我們打印轉賬金額,并調用 chainState() 方法。最后,清空文本框。
就醬,工作完成!請檢查你的代碼是否和圖中一致:

這里寫圖片描述
現在運行應用并測試一下。從前端看,這就像一個正常的交易應用,但是運行在屏幕背后的可是區塊鏈啊!請嘗試使用應用將 BTC 從一個帳戶轉移到另一個帳戶,隨意測試,盡情把玩吧!
結論
在這個教程中,你已經學會了如何使用 Swift 創建區塊鏈,并且創建了你自己的比特幣交易系統。請注意,真正加密貨幣的后端,和我們上面的實現完全不一樣,因為它需要用智能合約實現分布式,而本例僅用于學習。
在這個示例中,我們將區塊鏈技術應用于加密貨幣,然而你能想到區塊鏈的其他應用場景嗎?請留言分享給大家!希望你能學到更多新東西!
為了參考,你可以從 GitHub 下載完整的示例。