【huobi】构建数字化“分歧终端机”——探索Commit-Reveal Scheme
不知道看过电影《非诚勿扰》的人还记不记得“分歧终端机”这项奇葩发明,两片带把手的塑料片组成一个圆筒,有分歧的两个人将手伸入桶中,用石头剪刀布的方式同时出拳,然后拿开塑料片,根据猜拳结果决定听谁的。这玩意儿乍一看很荒谬,但是不得不说确实在一定程度上保证了猜拳的绝对公平性。
对于一个善于猜拳高手来说,哪怕只有短短半秒钟时间,也足够他观察出对手的手势进而一招制敌,赢得猜拳胜利。因此,在玩猜拳游戏的时候,双方都会尽可能的保证同时出拳。然而,在正常情况下,“绝对的同时”是不存在的,于是“分歧终端机”这个奇葩的发明似乎就有了用武之地。可如今,区块链为类似的公平性问题,提供了更好的解决方案——commitment scheme.
Commitment scheme
Commitment scheme是一种加密算法,它允许某人承诺某个值,同时将其对他人隐藏,并在以后可以对他人显示。commitment scheme中的值具有约束力,一旦提交,任何人都无法更改它们。此方案有两个阶段:一个提交阶段,该阶段中需要选择和指定一个值;另一个是揭示阶段,此阶段显示和检查该值。其实约等于一个数字化可记录版的“分歧终端机”。
如何在以太坊上构建一个“分歧终端机”?
Commitment scheme的功能听上去简单,但适用范围却很广。接下来就让我们手把手的教你,如何用Commitment scheme在以太坊上创建一个数字化版本的“分歧终端机”。对于想要学习区块链技术的人来说,这也不啻于一个有趣又实用的实操学习。
首先,从构造函数和初始化值开始。我们需要为“玩家”提供选择,以猜拳游戏为例,选项为:石头、布和剪刀。之后,要保证它们的功能只在各自的阶段中运行。最后,创建一个表示玩家承诺的结构,并在构造函数中设置一些初始值、状态变量以及事件。
具体操作如下:
contractRockPaperScissors{enumChoice{None,Rock,Paper,Scissors}enumStage{FirstCommit,SecondCommit,FirstReveal,SecondReveal,Distribute}structCommitChoice{addressplayerAddress;bytes32commitment;Choicechoice;}eventCommit(addressplayer);eventReveal(addressplayer,Choicechoice);eventPayout(addressplayer,uintamount);//Initialisationargsuintpublicbet;uintpublicdeposit;uintpublicrevealSpan;//StatevarsCommitChoice[2]publicplayers;uintpublicrevealDeadline;Stagepublicstage=Stage.FirstCommit;constructor(uint_bet,uint_deposit,uint_revealSpan)public{bet=_bet;deposit=_deposit;revealSpan=_revealSpan;}}
完成上述步骤之后,接下了需要结合检查功能来构建一个Commit Function. 该功能只被允许在两个提交阶段之一中运行。接着,我们可以确保其固有值随交易一同发送,并且将任何额外资金退回。在完成检查后,我们可以将承诺存储起来,发出Commit event,并进入下一个阶段。
具体操作如下:
functioncommit(bytes32commitment)publicpayable{//OnlyrunduringcommitstagesuintplayerIndex;if(stage==Stage.FirstCommit)playerIndex=0;elseif(stage==Stage.SecondCommit)playerIndex=1;elserevert("bothplayershavealreadyplayed");uintcommitAmount=bet+deposit;require(commitAmount>=bet,"overflowerror");require(msg.value>=commitAmount,"valuemustbegreaterthancommitamount");//Returnadditionalfundstransferredif(msg.value>commitAmount){(boolsuccess,)=msg.sender.call.value(msg.value-commitAmount)("");require(success,"callfailed");}//Storethecommitmentplayers[playerIndex]=CommitChoice(msg.sender,commitment,Choice.None);//EmitthecommiteventemitCommit(msg.sender);//Ifwe'reonthefirstcommit,thenmovetothesecondif(stage==Stage.FirstCommit)stage=Stage.SecondCommit;//Otherwisewemustalreadybeonthesecond,movetofirstrevealelsestage=Stage.FirstReveal;}
接着,从检查这步转到揭示功能。此功能仅在揭示阶段之一中运行,且只接受有效选择。然后,找到玩家数据,以便于检查他们承诺的哈希值,确定是否有效。如果该哈希值有效,对其进行存储,触发Reveal(揭示)事件,进入下一个阶段。
具体操作如下:
functionreveal(Choicechoice,bytes32blindingFactor)public{//Onlyrunduringrevealstagesrequire(stage==Stage.FirstReveal||stage==Stage.SecondReveal,"notatrevealstage");//Onlyacceptvalidchoicesrequire(choice==Choice.Rock||choice==Choice.Paper||choice==Choice.Scissors,"invalidchoice");//FindtheplayerindexuintplayerIndex;if(players[0].playerAddress==msg.sender)playerIndex=0;elseif(players[1].playerAddress==msg.sender)playerIndex=1;//Revertifunknownplayerelserevert("unknownplayer");//FindplayerdataCommitChoicestoragecommitChoice=players[playerIndex];//Checkthehashtoensurethecommitmentiscorrectrequire(keccak256(abi.encodePacked(msg.sender,choice,blindingFactor))==commitChoice.commitment,"invalidhash");//UpdatechoiceifcorrectcommitChoice.choice=choice;//EmitrevealeventemitReveal(msg.sender,commitChoice.choice);if(stage==Stage.FirstReveal){//Ifthisisthefirstreveal,setthedeadlineforthesecondonerevealDeadline=block.number+revealSpan;require(revealDeadline>=block.number,"overflowerror");//Movetosecondrevealstage=Stage.SecondReveal;}//Ifwe'reonsecondreveal,movetodistributestageelsestage=Stage.Distribute;}
最后需要的是分配功能。我们需要在确定获胜者之后将奖励支付给他们。此功能也同之前每一步一样,只能在分发阶段或者揭示阶段完成之后才能运行。通过对结果进行检查,判定出获胜的一方,之后计算奖励,触发Payout event,将资金发送到胜者的地址,并为下一次游戏将状态重置。
具体操作如下:
functiondistribute()public{//Todistributeweneed://a)TobeinthedistributestageOR//b)Stillinthesecondrevealstagebutpastthedeadlinerequire(stage==Stage.Distribute||(stage==Stage.SecondReveal&&revealDeadline<=block.number),"cannotyetdistribute");//Calculatevalueofpayoutsforplayersuintplayer0Payout;uintplayer1Payout;uintwinningAmount=deposit+2*bet;require(winningAmount/deposit==2*bet,"overflowerror");//Ifbothplayerspickedthesamechoice,returntheirdepositsandbetsif(players[0].choice==players[1].choice){player0Payout=deposit+bet;player1Payout=deposit+bet;}//Ifonlyoneplayermadeachoice,theywinelseif(players[0].choice==Choice.None){player1Payout=winningAmount;}elseif(players[1].choice==Choice.None){player0Payout=winningAmount;}elseif(players[0].choice==Choice.Rock){assert(players[1].choice==Choice.Paper||players[1].choice==Choice.Scissors);if(players[1].choice==Choice.Paper){//Rocklosestopaperplayer0Payout=deposit;player1Payout=winningAmount;}elseif(players[1].choice==Choice.Scissors){//Rockbeatsscissorsplayer0Payout=winningAmount;player1Payout=deposit;}}elseif(players[0].choice==Choice.Paper){assert(players[1].choice==Choice.Rock||players[1].choice==Choice.Scissors);if(players[1].choice==Choice.Rock){//Paperbeatsrockplayer0Payout=winningAmount;player1Payout=deposit;}elseif(players[1].choice==Choice.Scissors){//Paperlosestoscissorsplayer0Payout=deposit;player1Payout=winningAmount;}}elseif(players[0].choice==Choice.Scissors){assert(players[1].choice==Choice.Paper||players[1].choice==Choice.Rock);if(players[1].choice==Choice.Rock){//Scissorslosetorockplayer0Payout=deposit;player1Payout=winningAmount;}elseif(players[1].choice==Choice.Paper){//Scissorsbeatspaperplayer0Payout=winningAmount;player1Payout=deposit;}}elserevert("invalidchoice");//Sendthepayoutsif(player0Payout>0){(boolsuccess,)=players[0].playerAddress.call.value(player0Payout)("");require(success,'callfailed');emitPayout(players[0].playerAddress,player0Payout);}elseif(player1Payout>0){(boolsuccess,)=players[1].playerAddress.call.value(player1Payout)("");require(success,'callfailed');emitPayout(players[1].playerAddress,player1Payout);}//Resetthestatetoplayagaindeleteplayers;revealDeadline=0;stage=Stage.FirstCommit;}
接下来提供一个完整的合约内容供大家参考:
pragma solidity ^0.5.0;
contract RockPaperScissors {
enum Choice {
None,
Rock,
Paper,
Scissors
}
enum Stage {
FirstCommit,
SecondCommit,
FirstReveal,
SecondReveal,
Distribute
}
struct CommitChoice {
address playerAddress;
bytes32 commitment;
Choice choice;
}
event Payout(address player, uint amount);
//Initialisation args
uint public bet;
uintpublic deposit;
uint public revealSpan;
//State vars
CommitChoice[2] public players;
uint public revealDeadline;
Stage public stage = Stage.FirstCommit;
constructor(uint_bet, uint _deposit, uint _revealSpan) public {
bet = _bet;
deposit = _deposit;
revealSpan = _revealSpan;
}
function commit(bytes32 commitment) public payable {
// Only run during commit stages
uint playerIndex;
if(stage == Stage.FirstCommit) playerIndex = 0;
else if(stage == Stage.SecondCommit) playerIndex = 1;
else revert("both players have already played");
uint commitAmount = bet + deposit;
require(commitAmount >= bet, "overflow error");
require(msg.value >= commitAmount, "value must be greater thancommit amount");
// Return additional funds transferred
if(msg.value > commitAmount) {
(bool success, ) = msg.sender.call.value(msg.value -commitAmount)("");
require(success, "call failed");
}
// Store the commitment
players[playerIndex] = CommitChoice(msg.sender, commitment,Choice.None);
// If we're on the first commit, then move to the second
if(stage == Stage.FirstCommit) stage = Stage.SecondCommit;
// Otherwise we must already be on the second, move to first reveal
else stage = Stage.FirstReveal;
}
function reveal(Choice choice, bytes32 blindingFactor) public {
// Only run during reveal stages
require(stage == Stage.FirstReveal || stage == Stage.SecondReveal,"not at reveal stage");
// Only accept valid choices
require(choice == Choice.Rock || choice == Choice.Paper || choice ==Choice.Scissors, "invalid choice");
// Find the player index
uint playerIndex;
if(players[0].playerAddress == msg.sender) playerIndex = 0;
else if (players[1].playerAddress == msg.sender) playerIndex = 1;
// Revert if unknown player
else revert("unknown player");
// Find player data
CommitChoice storage commitChoice = players[playerIndex];
// Check the hash to ensure the commitment is correct
require(keccak256(abi.encodePacked(msg.sender, choice, blindingFactor))== commitChoice.commitment, "invalid hash");
// Update choice if correct
commitChoice.choice = choice;
if(stage == Stage.FirstReveal) {
// If this is the first reveal, set the deadline for the second one
revealDeadline = block.number + revealSpan;
require(revealDeadline >= block.number, "overflow error");
// Move to second reveal
stage = Stage.SecondReveal;
}
// If we're on second reveal, move to distribute stage
else stage = Stage.Distribute;
}
function distribute() public {
// To distribute we need:
// a) To be in the distribute stage OR
// b) Still in the second reveal stage but past the deadline
require(stage == Stage.Distribute || (stage == Stage.SecondReveal&& revealDeadline <= block.number), "cannot yetdistribute");
// Calculate value of payouts for players
uint player0Payout;
uint player1Payout;
uint winningAmount = deposit + 2 * bet;
require(winningAmount / deposit == 2 * bet, "overflow error");
// If both players picked the same choice, return their deposits andbets
if(players[0].choice == players[1].choice) {
player0Payout = deposit + bet;
player1Payout = deposit + bet;
}
// If only one player made a choice, they win
else if(players[0].choice == Choice.None) {
player1Payout = winningAmount;
}
else if(players[1].choice == Choice.None) {
player0Payout = winningAmount;
}
else if(players[0].choice == Choice.Rock) {
assert(players[1].choice == Choice.Paper || players[1].choice ==Choice.Scissors);
if(players[1].choice == Choice.Paper) {
// Rock loses to paper
player0Payout = deposit;
player1Payout = winningAmount;
}
else if(players[1].choice == Choice.Scissors) {
// Rock beats scissors
player0Payout = winningAmount;
player1Payout = deposit;
}
}
else if(players[0].choice == Choice.Paper) {
assert(players[1].choice == Choice.Rock || players[1].choice ==Choice.Scissors);
if(players[1].choice == Choice.Rock) {
// Paper beats rock
player0Payout = winningAmount;
player1Payout = deposit;
}
else if(players[1].choice == Choice.Scissors) {
// Paper loses to scissors
player0Payout = deposit;
player1Payout = winningAmount;
}
}
else if(players[0].choice == Choice.Scissors) {
assert(players[1].choice == Choice.Paper || players[1].choice ==Choice.Rock);
if(players[1].choice == Choice.Rock) {
// Scissors lose to rock
player0Payout = deposit;
player1Payout = winningAmount;
}
else if(players[1].choice == Choice.Paper) {
// Scissors beats paper
player0Payout = winningAmount;
player1Payout = deposit;
}
}
else revert("invalid choice");
// Send the payouts
if(player0Payout > 0) {
(bool success, ) =players[0].playerAddress.call.value(player0Payout)("");
require(success, 'call failed');
emit Payout(players[0].playerAddress, player0Payout);
} else if (player1Payout > 0) {
(bool success, ) =players[1].playerAddress.call.value(player1Payout)("");
require(success, 'call failed');
emit Payout(players[1].playerAddress, player1Payout);
}
// Reset the state to play again
delete players;
revealDeadline = 0;
stage = Stage.FirstCommit;
}
}
以太坊是一个公共区块链,可因此导致了对于隐私数据的管理困难。而有许多应用程序就像是上面提到的猜拳游戏一样,需要隐藏值才能正常运行,因此,commitment schemes就成为了一个卓绝的解决方案。
踢马河:RaTiO Fintech合伙人,曾任某券商自营操盘手,十余年海外对冲基金和国内大型投资机构基金经理,资深交易建模专家,币圈大咖。
请尊重原创!转载请注明出处。
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。