钱包的目的是为了给用户创建更高层的抽象接口来对交易进行管理。
我们最终的目的是让用户可以方便的:
创建一个新钱包
查看钱包的余额
在钱包之间进行交易
以上这些生效后,用户就不需要知道上一章节中描述的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」上的货币总量不会变,只是有些币被交易了,地址属于不同的用户了而已。
下图演示了上面所提及交易:
下面我们来看一个更复杂点的场景:
用户C最开始拥有0个币
之后的三个交易让C分别获得了10,20,30个币
C想要给D转发55个币。
这种情况下,用户C的所有3个outputs(在「未消费交易outputs」中address为C公钥的那些outputs)都为被用上才能凑够55个币给D,剩余的5个币则会还给C。
那么如何将以上的描述用代码逻辑来表示呢? 首先,我们会为交易创建相应的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。