欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 前端技术 > javascript >内容正文

javascript

初识区块链——用JS构建你自己的区块链

发布时间:2025/3/21 javascript 13 豆豆
生活随笔 收集整理的这篇文章主要介绍了 初识区块链——用JS构建你自己的区块链 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

初识区块链——用JS构建你自己的区块链

区块链太复杂,那我们就讲点简单的。用JS来构建你自己的区块链系统,寥寥几行代码就可以说明区块链的底层数据结构、POW挖矿思想和交易过程等。当然了,真实的场景远远远比这复杂。本文的目的仅限于让大家初步了解、初步认识区块链。

文章内容主要参考视频:Building a blockchain with Javascript (https://www.youtube.com/playlist?list=PLzvRQMJ9HDiTqZmbtFisdXFxul5k0F-Q4)

感谢原作者,本文在原视频基础上做了修改补充,并加入了个人理解。

认识区块链

 

区块链顾名思义是由区块连接而成的链,因此最基本的数据结构是Block。每个Block都含有timestamp、data、hash、previousHash等信息。其中data用来存储数据,previousHash是前一个区块的hash值。示意如下:

hash是对区块信息的摘要存储,hash的好处是任意长度的信息经过hash都可以映射成固定长度的字符串,如可用sha256:

calculateHash() {return SHA256(this.previousHash+ this.timestamp + JSON.stringify(this.data)).toString(); }

Block的数据结构

 

Block的最基本数据结构如下:

class Block {constructor(timestamp, data, previousHash = '') {this.timestamp = timestamp;this.data = data;this.previousHash = previousHash;//对hash的计算必须放在最后,保证所有数据赋值正确后再计算this.hash = this.calculateHash(); }calculateHash() {return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();} }

BlockChain的数据结构

 

多个Block链接而成BlockChain,显然可用用数组或链表来表示,如:

class BlockChain {constructor() {this.chain = [];} }

创世区块

 

正所谓万物始于一,区块链的第一个区块总是需要人为来手动创建,这个区块的previousHash为空,如:

createGenesisBlock() {return new Block("2018-11-11 00:00:00", "Genesis block of simple chain", ""); }

区块链的构造方法也应改为:

class BlockChain {constructor() {this.chain = [this.createGenesisBlock()];} }

添加区块

 

每新加一个区块,必须保证与原有区块链连接起来,即:

class BlockChain {getLatestBlock() {return this.chain[this.chain.length - 1];}addBlock(newBlock) {//新区块的前一个hash值是现有区块链的最后一个区块的hash值;newBlock.previousHash = this.getLatestBlock().hash;//重新计算新区块的hash值(因为指定了previousHash);newBlock.hash = newBlock.calculateHash(); //把新区块加入到链中;this.chain.push(newBlock); }... }​

校验区块链

 

区块链数据结构的核心是保证前后链接、无法篡改,但是如果有人真的篡改了某个区块,我们该如何校验发现呢?最笨也是最自然是想法就是遍历所有情况,逐一校验,如:

isChainValid() {//遍历所有区块for (let i = 1; i < this.chain.length; i++) {const currentBlock = this.chain[i];const previousBlock = this.chain[i - 1];//重新计算当前区块的hash值,若发现hash值对不上,说明该区块有数据被篡改,hash值未重新计算if (currentBlock.hash !== currentBlock.calculateHash()) {console.error("hash not equal: " + JSON.stringify(currentBlock));return false;}//判断当前区块的previousHash是否真的等于前一个区块的hash,若不等,说明前一个区块被篡改,虽然hash值被重新计算正确,但是后续区块的hash值并未重新计算,导致整个链断裂if (currentBlock.previousHash !== previousBlock.calculateHash) {console.error("previous hash not right: " + JSON.stringify(currentBlock));return false;}}return true; }

Just run it

 

跑起来看看,即:

let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));console.log(JSON.stringify(simpleChain, null, 4));console.log("is the chain valid? " + simpleChain.isChainValid());

结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js {"chain": [{"timestamp": "2018-11-11 00:00:00","data": "Genesis block of simple chain","previousHash": "","hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"},{"timestamp": "2018-11-11 00:00:01","data": {"amount": 10},"previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"},{"timestamp": "2018-11-11 00:00:02","data": {"amount": 20},"previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529","hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"}] } is the chain valid? true

注意看其中的previousHash与hash,确实是当前区块的previousHash指向前一个区块的hash。

篡改下试试

 

都说区块链不可篡改,是真的吗?让我们篡改第2个区块试试,如:

let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));console.log("is the chain valid? " + simpleChain.isChainValid());//将第2个区块的数据,由10改为15 simpleChain.chain[1].data = {amount: 15};console.log("is the chain still valid? " + simpleChain.isChainValid()); console.log(JSON.stringify(simpleChain, null, 4));

结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js is the chain valid? true hash not equal: {"timestamp":"2018-11-11 00:00:01","data":{"amount":15},"previousHash":"fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash":"150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"} is the chain still valid? false {"chain": [{"timestamp": "2018-11-11 00:00:00","data": "Genesis block of simple chain","previousHash": "","hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"},{"timestamp": "2018-11-11 00:00:01","data": {"amount": 15},"previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"},{"timestamp": "2018-11-11 00:00:02","data": {"amount": 20},"previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529","hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"}] }

显然,篡改了数据之后,hash值并未重新计算,导致该区块的hash值对不上。

再篡改下试试

 

那么,如果我们聪明点,篡改后把hash值也重新计算会如何?

let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));console.log("is the chain valid? " + simpleChain.isChainValid()); //篡改后重新计算hash值 simpleChain.chain[1].data = {amount: 15}; simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash(); console.log("is the chain still valid? " + simpleChain.isChainValid()); console.log(JSON.stringify(simpleChain, null, 4));

结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js is the chain valid? true previous hash not right: {"timestamp":"2018-11-11 00:00:02","data":{"amount":20},"previousHash":"150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529","hash":"274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"} is the chain still valid? false {"chain": [{"timestamp": "2018-11-11 00:00:00","data": "Genesis block of simple chain","previousHash": "","hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"},{"timestamp": "2018-11-11 00:00:01","data": {"amount": 15},"previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1"},{"timestamp": "2018-11-11 00:00:02","data": {"amount": 20},"previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529","hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"}] }

显然,第3个区块的previousHash并未指向第2个区块的hash。

是真的无法篡改吗

 

其实并不是,如果我们再聪明一点,把后续区块的hash值也重新计算一下,不就OK了吗? 确实如此,如:

let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));console.log("is the chain valid? " + simpleChain.isChainValid()); //篡改第2个区块 simpleChain.chain[1].data = {amount: 15}; simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash(); //并把第3个区块也重新计算 simpleChain.chain[2].previousHash = simpleChain.chain[1].hash; simpleChain.chain[2].hash = simpleChain.chain[2].calculateHash(); console.log("is the chain still valid? " + simpleChain.isChainValid()); console.log(JSON.stringify(simpleChain, null, 4));

结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main_1.js is the chain valid? true is the chain still valid? true {"chain": [{"timestamp": "2018-11-11 00:00:00","data": "Genesis block of simple chain","previousHash": "","hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89"},{"timestamp": "2018-11-11 00:00:01","data": {"amount": 15},"previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1"},{"timestamp": "2018-11-11 00:00:02","data": {"amount": 20},"previousHash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1","hash": "cc294e763c51e9357bf22d96073e643f4d51e07dd0de6e9b15d1d4f6bf6b45a8"}] }

现在看来,整个区块链确实完全被篡改了!!!事实上,如果你能做到篡改某个区块的时候,把后续所有的区块一起篡改掉,即可把整个区块链篡改掉。只不过,有的时候后续区块很多,你还要篡改的足够快,篡改的成本也非常高。因此,区块链并非完全不能被篡改,篡改是有“价格”的,更多是经济学上的考虑。在区块链的设计上,会尽可能地提高篡改的成本,让篡改的成本远远大于篡改的潜在收益,这样,整个区块链就可以被认为是安全的、不可篡改的。

工作量证明(Proof-of-Work)让区块链更安全

 

如前所述,区块链并非完全不可篡改,只是要提高篡改的成本。那如何提高成本呢?最笨的办法似乎就是人为地设置障碍,即:你想参与记账吗?那请先把这道复杂的数学题解出来,以证明你的实力和意愿。这就是最简单最朴素的工作量证明的思想。

出一道题

 

是什么样的数学题呢?并不是什么高深的题目,只是看起来傻傻的,只能靠猜、靠试才能解决的题目,比如:请保证hash值的前10位全是0。

大家都知道,hash计算具备如下典型特征:

  • 任意长度的信息,不管是一句话、一篇文章、还是一首歌,都可以计算出唯一的一串数字与之对应。

  • 这串数字的长度是固定的。

  • 计算过程是不可逆的,即你可以很容易计算出一段文本的hash值,但是你没有办法知道某个hash值对应的原始信息是什么。

因此,如果给你一段文本,允许你在文本最后加上一个随机数(nonce),来保证这段文本+随机数的hash值的前10位都是0,你没有什么好办法,只能不断地尝试不同的数字,然后期盼着运气好的话,能尽快试出来。

为区块增加随机数nonce

 

前面区块的hash计算是固定的,即:

calculateHash() {return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();}

该值无法改变,为了保证能解题,需要人为地在区块中加入随机数,即:

constructor(timestamp, data, previousHash = '') {this.timestamp = timestamp;this.data = data;this.previousHash = previousHash;this.nonce = 0;this.hash = this.calculateHash(); }

该随机数nonce并没有什么特别的含义,只是为了能改变下生成不同的hash值,以使得hash值满足要求。

相应的hash计算也做修改,即:

calculateHash() {return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString(); }​

解题即挖矿

 

如前所述,题目类似:请改变随机数nonce,以保证得出的hash值的前10位全是0。这用代码简单表达如下:

this.hash.substring(0, 10) === Array(10 + 1).join("0")

即hash值开头前10位全是0。

而至于到底是前10位还是前5位呢?显然,位数不同,难度不同。保证前10位为0的难度远远大于保证前5位为0。这个位数可以被称为难度系数(difficulty)。而挖矿的过程就是不同尝试nonce,以使得hash值满足条件的过程,即:

mineBlock(difficulty) {while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {this.nonce++;this.hash = this.calculateHash();}console.log("Block mined, nonce: " + this.nonce + ", hash: " + this.hash); }​

简单起见,可以把difficulty作为区块链的固定参数(注:事实上,在比特币中difficulty是动态调整的,这样来保证出块时间大致是10分钟),如:

constructor() {this.chain = [this.createGenesisBlock()];this.difficulty = 2; }

而添加区块的过程,不再是简单直接的add,而变成了挖矿的过程,即:

addBlock(newBlock) {newBlock.previousHash = this.getLatestBlock().hash;newBlock.mineBlock(this.difficulty);this.chain.push(newBlock); }

只有符合要求的区块才能被添加。

Just run it

 

跑起来试试,即:

let simpleChain = new BlockChain(); console.log("Mining block 1..."); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); console.log("Mining block 2..."); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20}));console.log(JSON.stringify(simpleChain, null, 4));

会发现,整个世界慢了下来,出块明显没有之前快速了,结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main_2.js Mining block 1... Block mined, nonce: 464064, hash: 0000e7e1aae4fae9d245f8d4b8ce030ffe13270218c362511db6840a824a1cdb Mining block 2... Block mined, nonce: 4305, hash: 000047b449537483d7f2861a12b53a59c971d3a928b2c0110a5945bff1a82616 {"chain": [{"timestamp": 0,"data": "2018-11-11 00:00:00","previousHash": "Genesis block of simple chain","nonce": 0,"hash": "8a7b66d194b1b0b795b0c45b3f11b60e8aa97d3668c831f39ec3343c83ae41c0"},{"timestamp": "2018-11-11 00:00:01","data": {"amount": 10},"previousHash": "8a7b66d194b1b0b795b0c45b3f11b60e8aa97d3668c831f39ec3343c83ae41c0","nonce": 464064,"hash": "0000e7e1aae4fae9d245f8d4b8ce030ffe13270218c362511db6840a824a1cdb"},{"timestamp": "2018-11-11 00:00:02","data": {"amount": 20},"previousHash": "0000e7e1aae4fae9d245f8d4b8ce030ffe13270218c362511db6840a824a1cdb","nonce": 4305,"hash": "000047b449537483d7f2861a12b53a59c971d3a928b2c0110a5945bff1a82616"}],"difficulty": 4 }

显然,这里difficulty为4,所得到的区块hash开头为4个0。

POW的思想

 

是的,这就是整个proof-of-work的思想。看似很笨很傻的思想,事实上已经被证明,足够的有效、足够的安全。比特币的pow在完全无人主导的情况下,协调了数百万台机器的一致性,历经10年没有出现过一次错误,这不能不说是个伟大的奇迹。事实上,这个最简单的思想背后蕴藏着更深刻的思想,pow的本质是一个cpu投一票(one-cpu-one-vote),即请用你的cpu(算力)来表达你的看法和意见。为什么是CPU,而不是one-ip-one-vote?因为IP太廉价、造假成本太低,你很容易虚拟出大量ip。之所以选择CPU,是因为在当时(2008年)看来,CPU资源是相当昂贵的资源,以此来保证挖矿的难度和公平性(这部分在比特币白皮书上中本聪已经说的非常清楚了https://bitcoin.org/bitcoin.pdf)。当然,中本聪可能当时没有想到ASIC等特定算法芯片的出现已经让普通的CPU挖矿变得越来越难,这里篇幅有限不做扩展。

因此POW的本质是什么?本质是提供了一种锚定。将虚拟世界的比特币与现实物理世界的CPU在某种程度上做了锚定,用现实物理世界的昂贵资源来保证比特币的安全性。有人说比特币挖矿太费电,完全是浪费。这其实是一种偏见,换一个角度讲,比特币可能是这个世界上最廉价的货币体系了。毕竟,美元的发行经历了流血与战争,背后还有巨大的昂贵的国家机器、航空母舰在做后盾。而比特币,只是消耗了一些算力、一些电费,并且这种消耗并非是完全无意义的,算力越大整个比特币体系也会越安全。

实际上,共识机制除了POW之外,比较常见的还有DPOS(delegate-proof-of-stake)等,甚至在联盟链中还有pfbt(Practical Byzantine Fault Tolerance)、raft等,这里不做扩展。

挖矿回报——利益驱动让区块链走得更远

 

如前所述的区块链过于简单,有如下大问题:

  • 每个区块只包含一次交易。这样会导致成本交易成本很高,事实上真实的区块链,每个区块会包含多笔交易,多笔交易会被同时打包到一个区块中。

  • 挖矿没有回报。如果挖矿没有回报,这个世界上谁会买矿机、耗电费为你的交易做校验、打包呢?世界需要雷锋,但世界的运转不能依靠雷锋,需要依靠的是实实在在的利益诱惑。合适的制度设计和激励制度是区块链稳健的根本。其实,在很多POW的加密货币中,挖矿是加密货币发行的唯一方式。比如比特币总共只有2100万个,只能通过挖矿不断挖出来,才能进入二级市场流通。

下面就会着重解决这两点。

定义Transaction

 

一个Transaction最基本的信息应包含:从谁转到了谁,转了多少钱,即:

class Transaction {constructor(fromAddress, toAddress, amount) {this.fromAddress = fromAddress;this.toAddress = toAddress;this.amount = amount;} }

而每个block应包含多个Transactions,即把之前的data改为transactions:

class Block {constructor(timestamp, transactions, previousHash = '') {this.timestamp = timestamp;this.transactions = transactions;this.previousHash = previousHash;this.nonce = 0;this.hash = this.calculateHash();}.... }

而blockchain的数据结构也需要做相应升级,需要增加待处理transactions和每次挖矿报酬额,即:

class BlockChain {constructor() {this.chain = [this.createGenesisBlock()];this.difficulty = 3;this.pendingTransactions = [];this.miningReward = 100;}.... }

请注意这种结构关系:

  • 1个Chain包含多个Block;

  • 1个Block包含多个Transaction;

挖矿

 

相应地,前面的addBlock方法应该被升级为minePendingTransactions,与之前相比的最大不同在于:

  • 新加的不是单纯的一个区块,而是包含了所有待处理交易信息的区块。(这里简单起见,把所有pendingTranactions都打包了一个区块中,真实场景并非如此,如比特币的原始区块大小只有1M,装不下的就要等待下一个区块打包了;另外矿工实际上通常是谁付费高就优先处理谁的交易)

  • 为矿工付费。一般而言,矿工挖出当前区块之后,会生成一批向矿工地址转账的交易,等待下个区块打包的时候转账。

如下:

//传入矿工地址 minePendingTransactions(miningRewardAddress) {//将所有待处理交易一起打包到同一个区块let block = new Block(Date.now(), this.pendingTransactions);//挖矿,即不断尝试nonce,以使得hash值满足要求block.mineBlock(this.difficulty);console.log('Block successfully mined!');this.chain.push(block);//将矿工费交易放入到pendingTransactions,待下次处理;矿工费交易的特点是来源账号为空;this.pendingTransactions = [new Transaction(null, miningRewardAddress, this.miningReward)]; } //创建交易,即将交易放入待处理交易池 createTransaction(transaction) {this.pendingTransactions.push(transaction); }

查询余额

 

既然有了转账,就自然会有查询某个账户余额的需求。不过在区块链中可能并不存在真的账户,常见的有比特币的UTXO模型和以太坊的账户余额模型。显然,在我们这里,也并不真的存在所谓的账户。这里的区块链交易只记录了从谁转到谁,转了多少钱,并没有记录哪个账户现在有多少钱。怎么才能知道某个账户的余额呢?最笨的方法就是遍历区块链所有的交易信息,根据from/to/amount来推算出某个账户的余额,即:

getBalanceOfAddress(address) {let balance = 0;for (const block of this.chain) {for (const transaction of block.transactions) {//账户转出,余额减少if (transaction.fromAddress === address) {balance -= transaction.amount;}//账户转入,余额增加if (transaction.toAddress === address) {balance += transaction.amount;}}}return balance;

Just run it

 

跑起来看看效果,转账真的成功了吗?矿工收到矿工费了吗?即:

let simpleCoin = new BlockChain(); //首先创建两笔交易,address1先向address2转账100,address2又向address1转账60。 simpleCoin.createTransaction(new Transaction('address1', 'address2', 100)); simpleCoin.createTransaction(new Transaction('address2', 'address1', 60));console.log('starting the miner...'); simpleCoin.minePendingTransactions('worker1-address'); //显然如果成功,address2应该有40。 console.log('Balance of address2 is: ', simpleCoin.getBalanceOfAddress('address2')); //矿工账户应该有多少钱呢?按说应该是矿工费100 console.log('Balance of miner is: ', simpleCoin.getBalanceOfAddress('worker1-address'));//再创建一笔交易,address2又向address1转账10 simpleCoin.createTransaction(new Transaction('address2', 'address1', 10));console.log('starting the miner again...'); simpleCoin.minePendingTransactions('worker1-address'); //显然如果成功,address2应该还剩30 console.log('Balance of address2 is: ', simpleCoin.getBalanceOfAddress('address2')); //此时矿工费应该多少呢?处理两个区块,应该有200吧? console.log('Balance of miner is: ', simpleCoin.getBalanceOfAddress('worker1-address'));

结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main_3.js starting the miner... Block mined, nonce: 2121, hash: 000cd629157ee59494dfc08329d4cf265180c26010935993171b6881f9bae578 Block successfully mined! Balance of address2 is: 40 Balance of miner is: 0 starting the miner again... Block mined, nonce: 1196, hash: 000d5f8278ea9bf4f30c9cc05b4cc36aab8831dc5860e42c775360eb85bc238e Block successfully mined! Balance of address2 is: 30 Balance of miner is: 100

可见,address2的余额符合预期;唯一稍特别的是旷工余额,为什么成功打包了,旷工余额还是0,没收到打包费呢?因为矿工费转账放入到了下一个区块,只有下一个区块被成功打包,前一个区块的旷工才能收到矿工费。

交易签名与验证

 

如前所述,似乎谁都可以发起交易,比如我想发起一笔从中本聪账户到我的账户交易,转账100个。是否可行呢?在前面的模型中,确实似乎谁都可以发起交易。事实上,这当然不可能是真的。截止目前的模型,还缺少重要一环,即必须对交易进行签名,以保证:你只能从你的自己的账户转出钱,你没有别人账户的密码就不可能操作别人的账户。

无法找回的密码

 

密码其实是现实世界的概念,比如银行卡密码、淘宝登录密码、自动门禁的密码,你必须妥善保管,一旦被人知道了财产可能损失;当然,你如果怀疑密码被盗,可以赶紧改下密码;如果真的记不起密码,还可以带上身份证去银行修改密码。

然而,在区块链的世界中,不存在改密码、找回密码的说法。更重要的是,在区块链的世界中没有身份证,密码本身就是身份证。

非对称加密

 

区块链世界的唯一密码就是私钥。私钥是如何而来的?是通过非对称加密生成的,非对称的意思就是加密和解密使用不同的密钥。听起来很复杂,其实思想很简单,如下:

  • 非对称加密算法会生成一对密钥,一个是公开密钥publicKey,一个是私有密钥privateKey。二者可以互相加密解密,即公钥加密的,只有对应私钥才能解开;私钥加密的,只有对应公钥才能打开。

  • 无法从公钥推导出私钥;但可以从私钥推导出公钥。(绝大多数对RSA的实现,都遵循pkcs的标准,即私钥能推出公钥,但公钥不能推出私钥)

  • 公钥用于加密,私钥用于解密:用公钥加密的数据,只有用相应的私钥才能解密。公钥类似邮箱地址,所有人都知道,谁都可以往里面寄信;但只有邮箱的主人才拥有密钥才能打开。

  • 私钥用于签名,公钥用于验证:东邪收到西毒的来信,但怎么确定这信真的是西毒写的呢?西毒把信用自己的密钥签名(其实就是加密),东邪收到信息之后,拿公开的西毒的公钥去试试能否解密,若能解密则确信是西毒的来信。

让我们简单地生成一对公钥私钥来看看,即:

const EC = require('elliptic').ec; const ec = new EC('secp256k1');const key = ec.genKeyPair(); const publicKey = key.getPublic('hex'); const privateKey = key.getPrivate('hex');console.log('Public key', publicKey); console.log('Private key', privateKey);

结果:

Public key 04f1aa4d934e7f2035a6c2a2ebc9daf0e9ca7d13855c2a0fb8696ab8763e5ee263c803dfa7ac5ae23b25fb98151c99f91c55e89586717965758e6663772ebccd1b Private key 1c258d67b50bda9377c1badddd33bc815eeac8fcb9aee5d097ad6cedc3d2310c

这个privateKey就是你的唯一密码,有32字节。而publicKey看起来似乎更长。但是平时看到的比特币地址似乎很短啊?是的,这里的publicKey有65字节,而开头1个字节是固定的0x04,除此之外的前32字节是椭圆曲线的X坐标,后32字节是椭圆曲线的Y坐标。比特币地址之所以更短,是因为又经过了SHA256加密、RIPEMD160加密和BASE58编码等等一系列的转化,最后生成了类似“1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa”这样的base58地址,这里简单起见不做扩展。通常而言,publicKey就是你的账户地址,只是格式的不同,可以进行可逆转化。

签名你的交易

 

首先,我们需要用自己的私钥,对发起的交易进行签名,以表明交易确实是由本人发起的,如:

class Transaction {//计算hash,为了做签名,因为不是直接对原始信息进行签名,而是对hash值签名。calculateHash() {return SHA256(this.fromAddress + this.toAddress + this.amount).toString();}//传入私钥signTransaction(signingKey) {//校验来源账户是否是本人的地址,即来源地址是否是该私钥对应的公钥if (signingKey.getPublic('hex') !== this.fromAddress) {throw new Error('You cannot sign transactions for other wallets!')}const txHash = this.calculateHash();//用私钥对交易hash进行签名const sig = signingKey.sign(txHash, 'base64');//将签名转成der格式this.signature = sig.toDER('hex');console.log("signature: "+this.signature)}... }

验证交易

 

随后,其他人收到该交易信息时,需要验证交易是否有效,即用来源账户的公钥来验证这笔交易的签名是否正确、是否真的是来自于fromAddress,如下:

class Transaction {isValid() {//矿工费交易fromAddress为空,不做校验if (this.fromAddress === null) return true;//判断签名是否存在if (!this.signature || this.signature.length === 0) {throw new Error('No signature in this transaction');}//对fromAddress转码,得到公钥(这一过程是可逆的,只是格式转化)const publicKey = ec.keyFromPublic(this.fromAddress, 'hex');//用公钥验证签名是否正确,即交易是否真的从fromAddress发起的return publicKey.verify(this.calculateHash(), this.signature);}... }

上面对单个交易的有效性进行了验证,而一个区块包含多笔交易,所以也需要增加对区块内所有交易验证的方法,如:

class Block {hasValidTransactions() {//遍历区块内所有交易,逐一验证for (const tx of this.transactions) {if (!tx.isValid()) {return false;}}return true;}... }

相应的,createTransaction也升级为addTransaction,即不再直接创建交易,而是要对已签名交易进行验证,有效的交易才提交。如:

class BlockChain {addTransaction(transaction) {if (!transaction.fromAddress || !transaction.toAddress) {throw new Error('Transaction must include from and to address');}//验证交易是否有效,有效的才能提交到交易池中if (!transaction.isValid()) {throw new Error('Cannot add invalid transaction to the chain');}this.pendingTransactions.push(transaction);}... }

相应的blockchain的isChainValid方法也应升级,加入区块内所有交易的验证,即:

class BlockChain {isChainValid() {for (let i = 1; i < this.chain.length; i++) {const currentBlock = this.chain[i];const previousBlock = this.chain[i - 1];//校验区块内的所有交易是否有效if (!currentBlock.hasValidTransactions()) {return false;}if (currentBlock.hash !== currentBlock.calculateHash()) {console.error("hash not equal: " + JSON.stringify(currentBlock));return false;}if (currentBlock.previousHash !== previousBlock.calculateHash()) {console.error("previous hash not right: " + JSON.stringify(currentBlock));return false;}}return true;}... }

Just run it

 

跑起来试试,如下:

const {BlockChain, Transaction} = require('./blockchain'); const EC = require('elliptic').ec; const ec = new EC('secp256k1'); //用工具生成一对私钥和公钥 const myPrivateKey = '1c258d67b50bda9377c1badddd33bc815eeac8fcb9aee5d097ad6cedc3d2310c'; const myPublicKey = '04f1aa4d934e7f2035a6c2a2ebc9daf0e9ca7d13855c2a0fb8696ab8763e5ee263c803dfa7ac5ae23b25fb98151c99f91c55e89586717965758e6663772ebccd1b';const myKey = ec.keyFromPrivate(myPrivateKey); //从私钥推导出公钥 const myWalletAddress = myKey.getPublic('hex'); //输出看下,确实从私钥得到了公钥 console.log("is the myWalletAddress from privateKey equals to publicKey?", myWalletAddress === myPublicKey);let simpleCoin = new BlockChain();const trumpPublicKey = '047058e794dcd7d9fb0a256349a5e2d4d724b50ab8cfba2258e1759e5bd4c81bb6ac1b0490518287ac48f0f10a58dc00cda03ffd6d03d67158f8923847c8ad4e7d'; //发起交易,从自己账户向trump转账60 const tx1 = new Transaction(myWalletAddress, trumpPublicKey, 60); //用私钥签名 tx1.signTransaction(myKey); //提交交易 simpleCoin.addTransaction(tx1);console.log('starting the miner...'); simpleCoin.minePendingTransactions(myWalletAddress); //若转账成功,trump账户余额应是60 console.log('Balance of trump is: ', simpleCoin.getBalanceOfAddress(trumpPublicKey));//发起交易,从trump账户向你的账户转回20 const tx2 = new Transaction(trumpPublicKey, myWalletAddress, 20); //仍用你的私钥签名,这里会报错,你并不知道trump的密钥,无法操作其账户,即你的密钥打不开trump的账户; tx2.signTransaction(myKey); simpleCoin.minePendingTransactions(myWalletAddress); console.log('Balance of trump is: ', simpleCoin.getBalanceOfAddress(trumpPublicKey));

结果如下:

ali-186590cc4a7f:simple-chain shanyao$ node main.js is the myWalletAddress from privateKey equals to publicKey? true signature: 3045022100b87a9199c2b3fa31ac4092b27a41a616d99df884732dfd65972dc9eacd12da7702201f7957ef25d42c17cb2f6fb2888e6a0d5c521225d9b8851ba2d228f96d878f85 starting the miner... Block mined, nonce: 15812, hash: 00081837c2ae46a1310a0873f5e3d6a1b14b072e3d32a538748fac71e0bfd91e Block successfully mined! Balance of trump is: 60 /Users/shanyao/front/simple-chain/blockchain.js:22throw new Error('You cannot sign transactions for other wallets!')

显然,第一次操作并签名的是自己的账户,有私钥能成功;第二次操作的是别人的账户,私钥不对,无法提交转账。私钥就是区块链世界的唯一密码、唯一通行证,只有拥有私钥的人才能拥有对应的资产,这也许就是真正的私有财产神圣不可侵犯。

总结

 

如果看到这里,是不是觉得区块链很简单?是的,没有想象中复杂,但其实也没那么简单。事实上,区块链真正核心的共识机制(分布式协调一致性)以及去中心化治理,本文并未涉及。本文只是简单介绍了区块链的基本结构、基本概念和大致交易过程,帮忙大家初步认识区块链,解开区块链神秘的面纱。而区块链本身是一个宏大的主题,还需要更多的研究,更多的思考和探索。

总结

以上是生活随笔为你收集整理的初识区块链——用JS构建你自己的区块链的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。