使用javascript實現小型區塊鏈

區塊鏈概念
狹義:區塊鏈是一種按照時間順序將數據區塊以順序相連的方式組合成的一種鏈式數據結構,並以密碼方式保證的不可篡改和不可僞造的分佈式帳本。

1、挖礦(產生新區塊)

首先,區塊鏈是由每個區塊聯繫而造成的,在產生新區塊以前必須先有一個最初始的區塊,這個區塊也叫創世區塊。經過這個創世區塊,不停地經過變化隨機數(nonce)來計算出符合條件的區塊。如下是創世區塊基本信息:node

const initBlock = {
    index: 0,
    data: 'hey,this is a block chain',
    previousHash: '0',
    timestamp: '1551806536961',
    nonce: 80490,
    hash: '0000352fb27dd1141fa7265833190a53e5776b1111e275db0d9a77bf840081e6'
};
  1. index:是指每一個區塊的序號
  2. data: 這裏存放着區塊中全部的信息,例如轉帳,餘額等數據
  3. previousHash: 指的是上一個區塊的hash值,創世區塊沒有上一個,顯示0便可
  4. timestamp:指的是建立這個區塊的時間
  5. nonce:這個是隨機數,挖礦就是經過不停變換這個nonce來計算出符合條件的哈希。
  6. hash: 本區塊的hash值,經過前面5個字段的信息進行hash運算得出的值。

接着,經過不停的hash運算計算出符合條件的哈希,即挖礦。挖礦也能夠調節難度的大小,例如算出的哈希值必須前3位數必須爲1或者末3位數必須爲1等等,這個能夠自行的去定義,只要最後留一個控制的開關,方便控制便可。能夠在定義一個變量git

哈希的計算:github

.createHash('sha256')
 .update(index + data + previousHash + timestamp + nonce)
 .digest('hex')
_that.difficulty = 3   // 即前3位或者末3位數必須爲1,數量越多難度越大

生成了符合條件的hash以後,則產生了新的區塊,可是還要對這個區塊進行校驗看看是否有效,由於可能這是一個被篡改的非法的區塊,也有可能和這個鏈沒有任何關係的區塊而僅僅只是符合上述哈希的規則而已。因此,須要進行一下校驗,,先後區塊的有效性。算法

isValidaBlock(newBlock,lastBlock) {
     if (newBlock.index !== lastBlock.index+1) return false
     if (newBlock.previousHash !== lastBlock.hash) return false
     if (newBlock.timestamp <= lastBlock.timestamp) return false
     if (newBlock.hash.slice(1 ,_that.difficulty) !== '1'.repeat(_that.difficulty)) return false
     if (newBlock.hash !== this.computeHashForBlock(newBlock)) return false  //確保隨機數正確
        // 都知足則返回true
        return true
    }

除了上面的校驗以外,還須要使用上面這個函數對整一個chain進行一個每個塊的校驗,以保證每個塊的信息是正確的,是沒有被篡改過的是合法的。安全

2、構建P2P網絡

區塊鏈的網絡是去中心化的,即沒有中心服務器的網絡,客戶端不須要依賴中心服務器來獲取或者處理數據。區塊鏈網絡中,有這許許多多的節點,每一個節點都是一個獨立的成員,他們既是客戶端也是服務器,節點與節點直接都是點對點進行鏈接(peer-to-peer),不須要經過某一箇中心服務器進行中轉,因此,信息安全的角度來講,點對點的鏈接方式對信息私密性是很是可靠的。服務器

圖片描述

雖然,區塊鏈是經過點對點的鏈接方式進行數據傳輸,可是,在這以前還須要一個東西做爲引導,這個就是種子節點。由於,兩個節點之間他們可能不是處在同一個域下,他們之間想要聯繫,必須有一方知道對方的ip和端口,這樣才能和對方聯繫上。節點ip和端口號,在這個節點建立出來以後,種子節點就會發給它在這個區塊鏈中全部節點的ip和端口號同時記錄下這個新夥伴的ip和端口號。那麼,新的節點拿到了這一份"通信錄"以後,就會給這個"通信錄"中的全部小夥伴發個消息,告訴他們有一位新的小夥伴加入,以後,其餘節點收到了這個信息,也會在本身的"通信錄"中加上新夥伴的ip和端口號,至關於加入了白名單。這樣新的節點接下來就能夠和任意的的節點進行通訊了。網絡

下面用代碼演示一下:數據結構

(res)=>{
  _that.remotePeerInfo = res.data.data   //1
  _that.addPeersList(res.peersList)             //2
  _that.boardCast(_that.remotePeerInfo)    //3
  _that.blockChainUpdate(blockChain,blockData)     //4
}

addPeersList(peers) {
    peers.forEach(peer => {
        if (!_that.peers.find(v => _that.isEqualPeer(peer, v))) {
            _that.peers.push(peer)
        }
    })
}

boardCast(remotePeerInfo) {
    this.peers.forEach(v => {
        this.send(action, v.port, v.address)
    })
}

blockChainUpdate(blockChain,blockData){
  if(newChain.length === 1 ){
    return
    }

    if(_that.isValidaChain(newChain) && newChain.length>_that.blockchain.length){
    _that.blockchain = Object.assign({}, newChain)
    }else{
    console.log('error')
    return
    }

    if (trans.every(v => _that.isValidTransfer(v))) {
    _that.data = trans
    }
}

1.保存種子節點傳來的此新節點的信息包括ip和端口號,由於,新節點的ip和端口號是會有改變的狀況。分佈式

2.接受種子節點傳來的節點列表,將列表的節點遍歷檢查一下,沒有相同的就寫進列表中。函數

3.將新節點的信息廣播到全部的節點上,同時接受到信息的節點更新一下節點列表

4.將區塊鏈上信息同步一份都本地,同時對種子節點傳來的blockchain進行每一個區塊的信息

3、轉帳交易

BTC的交易模型是使用的是UTXO

圖片描述

而這個小型區塊鏈的交易模型使用的是最簡單的方法。

區塊鏈中"現金」,它是一個虛擬的東西就是一個字符串,來源於挖礦。每次挖礦成功都會有必定的獎勵,獲得的這些「錢」就能夠在區塊鏈網絡中自由的轉帳交易。

在區塊鏈中,進行記錄轉帳交易的時候是須要一個加密的算法,把全部的信息進行加密以後再push到新區塊中的data中,從而完成一筆新交易的記錄。以BTC爲例,BTC的加密算法是使用elliptic這個加密算法,elliptic是一個非對稱性的加密算法,非對稱的加密算法的特色就是,私鑰是唯一的,只有擁有者才能夠和他私鑰對應的公鑰進行校驗 。 nodejs也有對應的庫在github上搜索elliptic便可。

{
  "privateKey": "34a425df3eb1f22fb6cb74b0e7298b16ffd7f3fb",
  "publicKey": "ac208623a38d2906b090dbcf3a09378dfe79b77bf39c2b753ef98ea94fe08dc3995a1bd05c917"
}

上面是一個生成好的密鑰對格式,僅做爲展現,我刪減了一部分長度。

使用銀行卡進行轉帳交易的時候,會有一個轉出的帳號和一個轉入的帳號,在區塊鏈中的記帳也會有這個帳號,這個帳號就是上面使用生成的密鑰對中的公鑰,公鑰就是地址,或者說公鑰表明的就是本身的錢包。

校驗的方法,首先使用字段「from」,「to」,「amount」的參數進行sign簽名,而後在每次挖礦(記帳)的時候,則使用verify(),經過前面的三個參數,和sig進行校驗

verify(type,data){
    swtich(type){
        case 'sign':
            const bufferMsg = Buffer.from(`${data.from}-${data.to}-${data.amount}`)
            let signature = Buffer.from(keypair.sign(bufferMsg).toDER()).toString('hex')
               this.signature =  signature
        break;
        case 'verify':
             const keypairTemp = ec.keyFromPublic(pub, 'hex')
                const bufferMsg = Buffer.from(`${data.from}-${data.to}-${data.amount}`)
             this.keypair = keypairTemp.verify(bufferMsg, sig)
        break;
        default;
    }
}

轉賬的時候須要3步,分別是校驗轉出帳戶是否有足夠的金額,轉出帳戶就是本地公鑰。若有則進行記帳而且使用兩個地址、金額、時間,還有簽名加密打包,以後進行全節點廣播。其餘節點收到這個信息以後第一件事也是對新區塊的有效性作一個校驗,經過校驗以後就會寫入data中。

transfer(data)  {
    const timestamp = new Date().getTime()
    const sig = rsa.sign({data.from, data.to, data.amount , timestamp})
    const sigTrans = {data.from, data.to, data.amount ,timestamp, sig }

        // 非創世區塊
    if (trans.from !== '0') {
            // 檢驗餘額
        if (!(_that.blance < amount)) { //_that.blance 當前帳戶餘額
            //全節點廣播
            _that.send('trans', sigTrans)
        }else{
            console.log('not enough blance')
            return
        }
    }
    this.data.push(sigTrans)
    return sigTrans
}

其餘節點收到消息以後,先進行去重校驗,而後再更新數據。

4、查詢餘額

這個鏈的查詢方法比較簡單,就是將區塊中的每一條交易的信息進行校驗和匹配,知足條件的就進行增減,同時忽略精度上的問題。

this.blance = blance(address)
 blance(address) {
        let blance = 0;
        this.blockchain.forEach(block => {
            block.data.forEach(trans => {
                if (address == trans.from) {
                    blance -= trans.amount
                }

                if (address == trans.to) {
                    blance += trans.amount
                }

            })

        });
        return blance
    }

至此,區塊鏈的最簡單的功能就實現完畢。

相關文章
相關標籤/搜索