欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

程序员文章站 2022-07-16 18:16:44
...

这里介绍的是两个简单的加密货币GoofyCoin,ScroogeCoin,主要注意如何避免双花问题(double-spent)在作业需要对ScroogeCoin的交易有效性进行验证。

GroofyCoin

Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

这种货币机制很简单,它有一个中心机构, Groofy 专门负责创建货币,而且仅有他能够创建,满足3条规则:

  • Groofy可以创建货币,通过在货币上签名
  • 任何货币的拥有者可以将其支付给其他人,通过简单的添加交易者的公钥,然后签上自己的名字
  • 任何人可以验证货币的有效性只需到达bock chain的末尾验证Groofy确实签署了他的签名。

double-spent 双花问题

Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

如上图,同一个Alice 的硬币,他可以给另外一个人Chunk。而且chuck也能验证这是有效的。这是任何货币都需要解决的。举这个简单的例子就是为了说明这个问题。

Scrooge Coin

这个货币的例子主要是为了解决加密货币的 double-spent问题

首先他也是有一个中心机构 : Scrooge,专门负责创建货币和验证交易的合法性。因此有两种类型的交易:

Create Coin
Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

这里做了一点优化是可以将多个硬币的创建放在一次交易信息中,每一个硬币后都有接受者的公钥。在实现中可以将transID用当前交易的hash来表示。

pay Coin

Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

这种交易也是合并了很多同时的交易,消费ID是说当前硬币一定在之前的某次交易中出现,且附有本次消费者的公钥
Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

类似于下图,也就是我们能通过某种手段经过消费ID找到之前的消费记录,也就是消费者的公钥pk,这个公钥是很有用的,因为每次交易都要要求有所有消费者的签名,而他们的公钥可以作为验证。

为了避免双花问题,这里Scrooge 做了一个未消费硬币的池子 UTXOPool 这个在后面的实验细节中有。总的来说,每次消费是有效的,需要满足以下一些规则:

Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现

  • 硬币在之前的交易中创建过
  • 不能有双花
  • 输入(消费者)的总金额大于等于输出(得到硬币的人)(大于意味着 剩余价值被SCrooge这个中心机构拿走了)
  • 本次交易被所有人签名

实现

下面讲讲他的实现

你将作业下载下来,你会发现有5个类:

  • Crypto.java 应用了java安全中自带的验证签名机制,不用管
  • Transaction.java: 每次交易的设计都在这里
    主要有如下几个数据
    private byte[] hash;//交易的ID本次交易所有数据的Hash
    private ArrayList<Input> inputs;//消费者信息,Input是在Transction中定义的,下同
    private ArrayList<Output> outputs;//接受者信息
  • Trancsaction.Input,里面包含3个field
        /** hash of the Transaction whose output is being used */
        public byte[] prevTxHash;//指向前一次交易的hash值
        /** used output's index in the previous transaction */
        public int outputIndex;//所指前一次交易中输出的第几项,就是outputs中的index
        /** the signature produced to check validity */
        public byte[] signature;//他对本次交易的签名,具体签名的的那些信息可见文件中的详细函数
  • Transaction.output
        /** value in bitcoins of the output */
        public double value;
        /** the address or public key of the recipient */
        public PublicKey address;//可用来验证签名

具体Scrooge是怎么通过input找到,Input的公钥来验证的呢,这个就是 UTXOPool 的功能了,这里面存了一个映射,记录当前还没有被所有者消费的硬币以及他的来源

  • UTXOPool.java
private HashMap<UTXO, Transaction.Output> H;//UTXO就是用Input的hash和index做的可hash和比较的数据结构。通过这个Pool就能找到对应的OUtput了

作业让我们实现的是两个函数

TxHandler

isValidTx

理清上面的结构之后解决这个问题已经不难了,逐一检查,就行,代码中已经写得很清楚了

 /**
     * @return true if:
     * (1) all outputs claimed by {@code tx} are in the current UTXO pool, 
     * (2) the signatures on each input of {@code tx} are valid, 
     * (3) no UTXO is claimed multiple times by {@code tx},
     * (4) all of {@code tx}s output values are non-negative, and
     * (5) the sum of {@code tx}s input values is greater than or equal to the sum of its output
     *     values; and false otherwise.
     */
    public boolean isValidTx(Transaction tx) {
        // IMPLEMENT THIS
        double sumOfOut =0;
        double sumOfIn = 0;
        Set<UTXO> used = new HashSet<>();
        for(int i=0 ; i<tx.numInputs() ; ++i){
            Transaction.Input input = tx.getInput(i);
            UTXO utxo = new UTXO(input.prevTxHash,input.outputIndex);
            if(!utxoPool.contains(utxo))return false;//check (1)
            Transaction.Output output =utxoPool.getTxOutput(utxo);// the consume coin correspond prev output coin;
            sumOfIn += output.value;//(5)
            if(!Crypto.verifySignature(output.address, tx.getRawDataToSign(i),
                    input.signature))return false;//check(2) 
            if(!used.add(utxo))return false;//check(3)
        }
        for(Transaction.Output output : tx.getOutputs()){
            if(output.value <0)return false;//check(5)
            sumOfOut+=output.value;
        }
        if(sumOfIn < sumOfOut)return false;//check(5);
        return true;
    }

handleTxs

这一个是给你组乱序的交易 TX[] 让你返回其中有效的交易,注意只检查一遍是不够的,因为新交易的加入 UTXOPool可能会使得后面又有新的合法交易出现。最简单的做法就是不动点算法,每次遍历,看看是否结果有更新,如果有继续遍历,直到没有为止,最终一定会收敛到全部加入,而且每次至少会加入一个交易,中的来说复杂度 O(n2max(In.size,out.size))

 public Transaction[] handleTxs(Transaction[] possibleTxs) {
        // IMPLEMENT THIS
        HashSet<Transaction>txVis = new HashSet<>();
        //fixed point algorithm,iter untill no new transaction is valid
        while (true) {
            boolean updated = false;
            for(Transaction tx: possibleTxs){
                if(txVis.contains(tx))continue;
                if(isValidTx(tx)){
                    txVis.add(tx);
                    updated = true;
                    //add unspent coin
                    for(int i=0 ; i<tx.numOutputs() ; ++i){
                        UTXO utxo = new UTXO(tx.getHash(),i);
                        utxoPool.addUTXO(utxo, tx.getOutput(i));
                    }
                    //delete spent coin
                    for(int i=0 ; i<tx.numInputs() ; ++i){
                        Transaction.Input input = tx.getInput(i);
                        UTXO utxo = new UTXO(input.prevTxHash,input.outputIndex);
                        utxoPool.removeUTXO(utxo);
                    }
                }
            }
            if(!updated)break;
        };
        Transaction[] ret = new Transaction[txVis.size()];
        int idx =0;
        for(Transaction tx : txVis)
            ret[idx++] = tx;
        return ret;
    }

总结

这个货币已经很接近现实中的货币了,现实中货币能干的事情他都能干,而且有效。同时他也有现实中货币最大的缺点:中心化 有一个中心机构,完全有中心机构控制,所以通货膨胀什么的,,,你懂得 :)