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

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

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

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

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

這里寫圖片描述
錢包后端
現(xiàn)在切換到 ViewController.swift,我們會發(fā)現(xiàn)所有的 outlet 都已經(jīng)連接好了。我們只需要處理交易,并將其輸出到控制臺。
然而在此之前,我們需要稍微研究一下比特幣的區(qū)塊鏈。比特幣是由一個總賬戶產(chǎn)生的,我們將這個賬號的編號稱為 0000。當你挖到一個 BTC,意味著你解決了數(shù)學問題,因此會發(fā)行一定數(shù)量的比特幣作為獎勵。這提供了一個發(fā)幣的高明方法,并且可以激勵更多人去挖礦。在我們的應用,讓我們把挖礦獎勵設為 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 的變量作為我們的區(qū)塊鏈,并將 reward 設為 100。我們需要一個主帳戶作為所有比特幣的來源:即創(chuàng)世帳戶 0000。里面有 1000 萬個比特幣。你可以把這個賬戶想象成一個銀行,所有因獎勵產(chǎn)生的 100 個比特幣都經(jīng)此發(fā)放到合法賬戶中。我們還定義了一個提醒彈窗,每當交易無法完成時就會彈出。
現(xiàn)在,讓我們來編寫幾個運行時需要的通用函數(shù)。你能猜出是什么函數(shù)嗎?
- 第一個函數(shù)是用來處理交易的。我們需要確保交易雙方的賬戶,能夠接收或扣除正確的金額,并將這些信息記錄到我們的區(qū)塊鏈中。
- 下一個函數(shù)是在控制臺中打印整個記錄 —— 它將顯示每個區(qū)塊及其中的數(shù)據(jù)。
- 最后一個是用于驗證區(qū)塊鏈是否有效的函數(shù),通過校驗下一個區(qū)塊的 previousHash 和上一個區(qū)塊 hash 是否匹配。由于我們不會演示任何黑客方法,因此在我們的示例程序中,區(qū)塊鏈是永遠有效的。
交易函數(shù)
下面是一個通用的交易函數(shù),請在我們定義的變量下方輸入以下代碼:
- 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")
- }
- }
代碼量看起來好像很大,但主要是定義了每個交易需要遵循的一些規(guī)則。一開始是函數(shù)的四個參數(shù):
to,from,amount,type。前三個參數(shù)不需要再解釋了,而 Type 主要用于定義交易的類型。總共有兩個類型:正常類型(normal) 和創(chuàng)世類型(genesis)。正常類型的交易會發(fā)生在賬戶 1065 和 2017 之間,而創(chuàng)世類型將會涉及到賬戶 0000。
- 第一個 if-else 條件語句處理轉出賬戶的信息。如果賬戶不存在或者余額不足,將會提示交易不合法并返回。
- 第二個 if-else 條件語句處理轉入賬戶的信息。如果賬戶不存在,則創(chuàng)建新賬戶并轉入相應的比特幣。反之,則向該賬戶轉入正確數(shù)量的比特幣。
- 最后一個 if-else 條件語句處理交易類型。如果類型是創(chuàng)世類型,則添加一個創(chuàng)世區(qū)塊,否則創(chuàng)建一個新的區(qū)塊存儲數(shù)據(jù)。
打印函數(shù)
為了確保交易正確執(zhí)行,在每個交易結束后,我們希望拿到所有交易的清單。以下是我們在交易函數(shù)下方的代碼,用來打印相關信息:
- 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())
- }
這是一個簡單的循環(huán)語句,遍歷 bitcoinChain 中的所有區(qū)塊,并打印區(qū)塊號碼,哈希,前置哈希,以及存儲的數(shù)據(jù)。同時我們更新了界面中的標簽(label),這樣就可以顯示賬戶中正確的 BTC 數(shù)量。最后,打印所有的賬戶(應該是 3 個),并校驗區(qū)塊鏈的有效性。
現(xiàn)在你應該會在函數(shù)的最后一行發(fā)現(xiàn)一個錯誤。這是由于我們還沒有實現(xiàn) chainValidity() 函數(shù),讓我們馬上開始吧。
有效性函數(shù)
判斷一個鏈是否有效的標準是:前置區(qū)塊的哈希與當前區(qū)塊所表示的是否匹配。我們可以再次用循環(huán)語句來遍歷所有的區(qū)塊:
- 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 中的所有區(qū)塊,并檢查了前置區(qū)塊的 hash 是否與當前區(qū)塊的 previousHash 一致。
就醬!我們已經(jīng)將定義了所有需要的函數(shù)!你的 ViewController.swift 應該如下圖一樣:

這里寫圖片描述
收尾工作就是連接按鈕和函數(shù)啦。讓我們馬上開始最后的部分吧!
讓一切關聯(lián)起來
當我們的應用第一次啟動時,需要創(chuàng)世賬戶 0000 發(fā)送 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))
- }
我們使用已定義好的函數(shù)轉賬,并調(diào)用 chainState() 函數(shù)。最后,我們還在 invalidAlert 中添加了一個標題為 OK 的 UIAlertAction。
現(xiàn)在讓我們來實現(xiàn)剩下的四個函數(shù):ReMeNe()、BrimeMeNe()、ReSdEnter()和BuLeScript()。
挖礦函數(shù)
挖礦函數(shù)特別簡單,只需要三行代碼。添加以下代碼:
- @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()
- }
在第一個挖礦函數(shù)中,我們使用交易函數(shù)從創(chuàng)世賬戶發(fā)送了 100 BTC 到第一個賬戶。我們打印了挖礦的區(qū)塊,然后打印了區(qū)塊鏈的狀態(tài)。同樣地,在 blueMine 函數(shù)中,我們轉給了第二個賬戶 100 BTC。
發(fā)送函數(shù)
發(fā)送函數(shù)和挖礦函數(shù)略微相似:
- @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 的文本值是否為空。如果為空,則彈出無效交易的提示框。如果不為空,則繼續(xù)下一步。我們使用交易函數(shù)從第一個賬戶轉賬到第二個賬戶(或者反向轉賬),金額為輸入的數(shù)量。我們打印轉賬金額,并調(diào)用 chainState() 方法。最后,清空文本框。
就醬,工作完成!請檢查你的代碼是否和圖中一致:

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