Bitcoin and Cryptocurrency Technologies 学习笔记:1.5 ScroogeCoin 一个简单的加密货币设计实现
这里介绍的是两个简单的加密货币GoofyCoin,ScroogeCoin,主要注意如何避免双花问题(double-spent)在作业需要对ScroogeCoin的交易有效性进行验证。
GroofyCoin
这种货币机制很简单,它有一个中心机构, Groofy 专门负责创建货币,而且仅有他能够创建,满足3条规则:
- Groofy可以创建货币,通过在货币上签名
- 任何货币的拥有者可以将其支付给其他人,通过简单的添加交易者的公钥,然后签上自己的名字
- 任何人可以验证货币的有效性只需到达bock chain的末尾验证Groofy确实签署了他的签名。
double-spent 双花问题
如上图,同一个Alice 的硬币,他可以给另外一个人Chunk。而且chuck也能验证这是有效的。这是任何货币都需要解决的。举这个简单的例子就是为了说明这个问题。
Scrooge Coin
这个货币的例子主要是为了解决加密货币的 double-spent
问题
首先他也是有一个中心机构 : Scrooge,专门负责创建货币和验证交易的合法性。因此有两种类型的交易:
Create Coin
这里做了一点优化是可以将多个硬币的创建放在一次交易信息中,每一个硬币后都有接受者的公钥。在实现中可以将transID用当前交易的hash来表示。
pay Coin
这种交易也是合并了很多同时的交易,消费ID是说当前硬币一定在之前的某次交易中出现,且附有本次消费者的公钥
类似于下图,也就是我们能通过某种手段经过消费ID找到之前的消费记录,也就是消费者的公钥,这个公钥是很有用的,因为每次交易都要要求有所有消费者的签名,而他们的公钥可以作为验证。
为了避免双花问题,这里Scrooge 做了一个未消费硬币的池子 UTXOPool
这个在后面的实验细节中有。总的来说,每次消费是有效的,需要满足以下一些规则:
- 硬币在之前的交易中创建过
- 不能有双花
- 输入(消费者)的总金额大于等于输出(得到硬币的人)(大于意味着 剩余价值被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可能会使得后面又有新的合法交易出现。最简单的做法就是不动点算法,每次遍历,看看是否结果有更新,如果有继续遍历,直到没有为止,最终一定会收敛到全部加入,而且每次至少会加入一个交易,中的来说复杂度
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;
}
总结
这个货币已经很接近现实中的货币了,现实中货币能干的事情他都能干,而且有效。同时他也有现实中货币最大的缺点:中心化 有一个中心机构,完全有中心机构控制,所以通货膨胀什么的,,,你懂得 :)
上一篇: 机器学习实战(十一)利用PCA来简化数据
下一篇: 重新审视PCA