本文系博主在Medium上找到,翻译并作出了一定修改和解读的文章。原文链接附于末尾。
本系列教程的目标是帮助你构建一个如何开发区块链的蓝图。
在第二章我们将会:
- 创建一个简单的钱包
- 用我们的区块链发送签名标识的交易
- 感到更cool 从上一篇教程中,我们得到了一个基础的可验证的区块链,但是目前我们的区块链只包含无用的信息,今天我们将把他们替换成交易(我们的区块链将可以保存多个交易),允许我们创建一个非常简单的加密货币(crypto-currency),我们将称我们的货币为:NoobCoin。
注意,你需要引入bounceycastle和GSON包.
1. 准备一个钱包
在加密货币中,货币的所有权以交易业务的形式传递,参与者们有一个可供货币发送和接收的地址。最基本的钱包只是存储地址(静态钱包),更多的钱包,则同时是可以在区块链上进行交易的软件。
所以让我们创建一个存储公钥和私钥的Wallet类:
1 | package noobchain; |
公钥和私钥都是干什么的?
对于我们的‘noobCoin’而言公钥相当于地址,为了和其他人交易,这个公钥是允许分享的。私钥用来签名我们的交易,所以除了私钥的主人,没有人可以花我们的noobCoin。用户必须保持私钥是秘密的,在交易的过程中我们也发送公钥,用来验证我们的签名是合法的,没有被篡改攻击。
签名是通过私钥,发送地址,接受地址和交易值决定的,任何人可以通过公钥+签名+交易发送、接收地址和值来校验签名是否正确。
我们生成一对私钥和公钥,利用 Elliptic-curve cryptography来生成键对。现在让我们对钱包类加上一个generateKeyPair()
方法并在构造方法中调用他:
1 | package noobchain; |
你要理解这个方法是用了java.security.KeyPairGenerator去生成椭圆曲线键对(Elliptic Curve KeyPair),这个方法产生和设置我们的公钥和私钥。
现在我们已经构造出了钱包类的基本轮廓,接下来看交易。
2. 交易和签名
每一笔交易包含下列信息:
- 货币的发送者公钥(地址)
- 货币接受者的公钥(地址)
- 交易的值/总数
- 输入,能够证明发送者有足够的货币去发送的之前的关联交易
- 输出,显示这笔交易接收方关联地址的总数(这些输出被作为新交易的输入)
- 一个加密算法,证明该笔交易发送方地址是持有者本人,数据未被篡改。(比如,防止第三方修改交易总额)
现在我们创建这个新的交易(Transaction)类:
1 | import java.security.*; |
我们也应该创建空的TransactionInput和TransactionOutput类,不用担心,我们将会在之后补全它。
我们的交易类将同时包含用于生成、验证签名,验证交易的关联方法。
但是,等一下。。
签名的目的是什么?它如何工作?
签名承担了区块链中两项重要的任务:
第一,它们仅允许拥有者花费他们的货币,第二,防止其他人在一个新区块被挖掘(在入口点)之前篡改他们已提交的交易。
私钥用于签名数据,公钥用于验证它的完整性。
比如:Bob想要给Sally两个NoobCoins,所以他们的钱包软件生成了这次交易,把他们提交给了下一个区块的矿工,一个矿工想要修改这两个货币给John,然而幸运的是,Bob通过他的私钥给交易信息加了签名,并允许任何人通过公钥校验该数据是否被篡改(任何其他人的公钥都不能验证该笔交易)。
从上一个代码模块中我们可以知道我们的签名是一串字节,所以我们创建一个方法去生成它们,首先我们需要用到StringUtil类中的一些辅助方法:
1 | //Applies ECDSA Signature and returns the result ( as bytes ). |
不用担心看不懂他们,你真正需要了解的是:applyESDSASig接受发送方的私钥和输出,签名它们并返回一串字节。verifyECDSASig接受签名、公钥和数据并返回签名有效或者无效。getStringFromKey返回任意key经过重新编码的字符串。
现在我们在Transaction类中应用这些工具方法,通过增加一个generateSignature()
和verifySignature()
方法:
1 |
|
实际应用中,你可能需要为更多的信息做签名,比如用到的输入输出信息和时间戳
在新的交易被加到区块链上时,签名将会被矿工们校验。
3. 测试钱包和签名
现在我们基本已经做了一半了,测试一下。在NoobChain类里增加一些新的变量,更换main方法里的一些代码:
1 | import java.security.Security; |
我们创建了两个钱包,walletA
和walletB
,生成一笔交易并且用A的私钥加密。
现在我们只需要创建\验证输出和输入,并把交易存储在区块链中即可。
4. 输出&输出 1:加密货币是如何被拥有的
如果你想要拥有1个比特币,你需要接受1个比特币。总账不会真的给你的账户上加上一个比特币并在发送方那里减去一个。发送方参照他之前收到一个比特币,之后一个显示1个比特币被发送到你的账户的交易就被创建了。(交易输入参照之前的交易输出)
你的钱包余额是所有未花费的输出地址指向你的交易之和。
我们将遵循比特币约定,将所有未花费的输出交易称为UTXO’s。
我们创建一个交易输入类:
1 | public class TransactionInput { |
以及一个交易输出类:
1 | import java.security.PublicKey; |
交易输出将显示该交易发送给各方的总额,这些将要被作为一笔新交易的输入参照的交易,是你有可供支付的货币的证明。
5. 输出&输出 2:处理交易
链表中的区块将接收到很多交易,这使他可能变得非常长,这可能花费成吨的时间用来找出和检查它的输入。为了解决这个问题我们将维护一个可以用作输入的额外的未花费的交易集合。在我们的NoobChain类中增加所有UTXOs的集合:
1 |
|
我们把所有从来处理交易的类放在一个叫processTransaction的返回布尔值的方法中,把它放在交易(Transaction)类里:
1 | //Returns true if new transaction could be created. |
同时也增加了一个getInputsValue方法
通过这个方法我们做了一些检查以确保交易是合法的,然后收集输入并生成输出。
重要的是,在结尾我们舍弃了从UXTO’s的列表产生的输入,这意味着一个交易输出只能被用作一次输入。因此输入的完整值必须被使用,所以发送方可以返还给他们‘零钱’.
最后我们更新我们的钱包:
- 收集我们的账目(通过遍历UTXOs列表,并且检查交易输出是否为自己产生的)
- 然后生成我们的交易
1 | import java.security.*; |
6. 增加交易到我们的区块链中:
现在我们有了个可工作的交易系统,我们需要把它引入到我们的区块链中。我们需要用交易列表来替代我们区块中无用的数据。然而一个区块种可能有1000笔交易,这意味着哈希值需要很多运算。不过不用担心,我们可以用交易的merkle根。
我们增加一个生成mekle根的帮助方法到StringUtils类中:
1 | //Tacks in array of transactions and returns a merkle root. |
现在这个方法可以使用,不过很快就会被替换成真正的merkle根
现在引入到block类中:
1 | import java.util.ArrayList; |
注意我们同时更新了区块构造方法,因为我们不再需要字符串传递,并且在计算哈希方法中加入了merkle根。
我们的返回布尔值的addTransaction方法将增加交易,并且只当成功添加交易时返回true.
7. 压轴好戏
我们需要测试发送给或者从钱包发送货币,然后更新我们的区块链校验。不过首先我们需要一种在组合中引入新货币的方式。有很多种方式可以创建新货币,用比特币举例:矿工们可以在每一个成功挖出的区块中创建一笔交易给自己作为奖赏。不过现在我们只需要释放我们所需要的全部货币在第一个区块。和比特币一样,我们要硬编码创世块。
我们更新noobChain需要的一切:
- 创世块,释放100个NoobCoins给钱包A
- 更新过后的用作账户交易的合法性验证方法
- 用来测试一切是否工作正常的测试类
1 | public class NoobChain { |
钱包现在可以当且仅当它们有现金用作发送时,安全地发送交易给你的区块链了。这意味着你拥有了自己的加密货币。
现在我们完成了区块链的交易部分!
你成功的创建了你的加密货币,你的区块链现在:
- 允许用户通过new Wallet()创建新钱包
- 通过Elliptic-Curve加密方法给钱包提供公钥和私钥。
- 为货币交易的安全起见,通过数字签名算法保证归属性
- 最后允许所有用户通过‘Block.addTransaction(walletA.sendFunds(walletB.publicKey,20))’在你的区块链上创建交易。
你可以在GitHub上下载这些项目文件。
TO BE CONTINUED…
本文链接: https://dominicpoi.com/2019/03/30/JAVA-chainBlock-02/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
