第四章 自己动手写比特币之钱包

概览

钱包的目的是为了给用户创建更高层的抽象接口来对交易进行管理。

我们最终的目的是让用户可以方便的:

创建一个新钱包

查看钱包的余额

在钱包之间进行交易

以上这些生效后,用户就不需要知道上一章节中描述的inputs和outpus这些交易的细节,就能对交易进行管理了。就好比在比特币网络中,你只需要把比特币打入对应地址就能给别人打入比特币, 同时,你只需要将你自己的地址发布出去,别人就能给你打比特币了。

本章节完整的代码请看这里

生成钱包

本教程中我们将会用最简单的方法对钱包进行初始化和存储:将未加密的私钥保存在node/wallet/private_key这个文件中。

const privateKeyLocation = 'node/wallet/private_key'; const generatePrivatekey = (): string => { const keyPair = EC.genKeyPair(); const privateKey = keyPair.getPrivate(); return privateKey.toString(16); }; const initWallet = () => { //let's not override existing private keys if (existsSync(privateKeyLocation)) { return; } const newPrivateKey = generatePrivatekey(); writeFileSync(privateKeyLocation, newPrivateKey); console.log('new wallet with private key created'); };

如之前所言,我们的公钥(钱包地址)是通过私钥演绎出来的。

const getPublicFromWallet = (): string => { const privateKey = getPrivateFromWallet(); const key = EC.keyFromPrivate(privateKey, 'hex'); return key.getPublic().encode('hex'); };

需要提一提的是,把私钥明文保存在文件中是一个非常不安全的做法。我们这样子做只是为了演示的简单起见而已。同时,一个钱包当前只支持一个私钥,所以,如果你需要一个新的公钥来作为地址的话,必须要创建一个新的钱包。

钱包余额

复习下上一章节提到的一个说法:你在区块链中拥有的加密货币, 指的其实就是在「未消费交易outputs」中,接收者地址为自己的公钥的一系列outputs。

这意味着,我们如果要查看钱包余额的话,事情就变得非常简单了:你只需要将该地址下的所有「未消费交易output」记录的货币数加起来就完了。

const getBalance = (address: string, unspentTxOuts: UnspentTxOut[]): number => { return _(unspentTxOuts) .filter((uTxO: UnspentTxOut) => uTxO.address === address) .map((uTxO: UnspentTxOut) => uTxO.amount) .sum(); };

为了让演示更简单,我们在查询一个钱包地址的余额时并不需要提供任何私钥信息。也就是说,任何人都可以查看别人的账户余额。

生成交易

进行加密货币交易时,用户不应该需要关心交易中的inputs和outputs这些细枝末节。但是,当用户A的账户有50个币时,如果他要给用户B发送10个币时,交易后面究竟发生了什么事情呢?

这种情况下,系统会将10个币发送到B的公钥地址,同时会将剩余的40个币还给用户A。也就是说,来源的50个币必须消费完,所以在将来源的币赋给交易的outputs时必须进行拆分。交易完后必须将来源50币的output在「未消费交易outputs」中删除,将新产生的两个outputs加上去。 也就是说「未消费交易outputs」上的货币总量不会变,只是有些币被交易了,地址属于不同的用户了而已。

下图演示了上面所提及交易:

image

下面我们来看一个更复杂点的场景:

用户C最开始拥有0个币

之后的三个交易让C分别获得了10,20,30个币

C想要给D转发55个币。

这种情况下,用户C的所有3个outputs(在「未消费交易outputs」中address为C公钥的那些outputs)都为被用上才能凑够55个币给D,剩余的5个币则会还给C。

image

那么如何将以上的描述用代码逻辑来表示呢? 首先,我们会为交易创建相应的inputs。怎么创建呢?我们会遍历「未消费交易outputs」中地址为发送者公钥的项,直到找到能够凑够足够的币数(outputs的币数加起来大于等于目标币数)的outputs。

const findTxOutsForAmount = (amount: number, myUnspentTxOuts: UnspentTxOut[]) => { let currentAmount = 0; const includedUnspentTxOuts = []; for (const myUnspentTxOut of myUnspentTxOuts) { includedUnspentTxOuts.push(myUnspentTxOut); currentAmount = currentAmount + myUnspentTxOut.amount; if (currentAmount >= amount) { const leftOverAmount = currentAmount - amount; return {includedUnspentTxOuts, leftOverAmount} } } throw Error('not enough coins to send transaction'); };

如代码所示,我们除了找到满足条件的那些未消费的交易outputs,还会记录下交易后剩余的需要还给发送者的币数leftOverAmount。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwywyy.html