使用Javascript實現小型區塊鏈

架構師springboot發表於2019-04-03

區塊鏈概念

狹義:區塊鏈是一種按照時間順序將資料區塊以順序相連的方式組合成的一種鏈式資料結構,並以密碼方式保證的不可篡改和不可偽造的分散式賬本。

一、挖礦(產生新區塊)

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

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等等,這個可以自行的去定義,只要最後留一個控制的開關,方便控制即可。可以在定義一個變數

雜湊的計算:

.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進行一個每一個塊的校驗,以保證每一個塊的資訊是正確的,是沒有被篡改過的是合法的。

二、構建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進行每個區塊的資訊

三、轉賬交易

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  } 複製程式碼

其他節點收到訊息之後,先進行去重校驗,然後再更新資料。

四、查詢餘額

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

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     } 複製程式碼

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

覺得不錯請點贊支援,歡迎留言或進我的個人群855801563領取【架構資料專題目合集90期】、【BATJTMD大廠JAVA面試真題1000+】,本群專用於學習交流技術、分享面試機會,拒絕廣告,我也會在群內不定期答題、探討。


相關文章