构建数字化“分歧终端机”——探索Commit-Reveal Scheme
?
不知道看过电影《非诚勿扰》的人还记不记得“分歧终端机”这项奇葩发明,两片带把手的塑料片组成一个圆筒,有分歧的两个人将手伸入桶中,用石头剪刀布的方式同时出拳,然后拿开塑料片,根据猜拳结果决定听谁的。这玩意儿乍一看很荒谬,但是不得不说确实在一定程度上保证了猜拳的绝对公平性。
对于一个善于猜拳高手来说,哪怕只有短短半秒钟时间,也足够他观察出对手的手势进而一招制敌,赢得猜拳胜利。因此,在玩猜拳游戏的时候,双方都会尽可能的保证同时出拳。然而,在正常情况下,“绝对的同时”是不存在的,于是“分歧终端机”这个奇葩的发明似乎就有了用武之地。可如今,区块链为类似的公平性问题,提供了更好的解决方案——commitment scheme.?
Commitment scheme
Commitment scheme是一种加密算法,它允许某人承诺某个值,同时将其对他人隐藏,并在以后可以对他人显示。commitment scheme中的值具有约束力,一旦提交,任何人都无法更改它们。此方案有两个阶段:一个提交阶段,该阶段中需要选择和指定一个值;另一个是揭示阶段,此阶段显示和检查该值。其实约等于一个数字化可记录版的“分歧终端机”。
如何在以太坊上构建一个“分歧终端机”?
Commitment scheme的功能听上去简单,但适用范围却很广。接下来就让我们手把手的教你,如何用Commitment scheme在以太坊上创建一个数字化版本的“分歧终端机”。对于想要学习区块链技术的人来说,这也不啻于一个有趣又实用的实操学习。
首先,从构造函数和初始化值开始。我们需要为“玩家”提供选择,以猜拳游戏为例,选项为:石头、布和剪刀。之后,要保证它们的功能只在各自的阶段中运行。最后,创建一个表示玩家承诺的结构,并在构造函数中设置一些初始值、状态变量以及事件。
具体操作如下:
contract?RockPaperScissors?{????enum?Choice?{????????None,????????Rock,????????Paper,????????Scissors????}?????enum?Stage?{????????FirstCommit,????????SecondCommit,????????FirstReveal,????????SecondReveal,????????Distribute????}?????struct?CommitChoice?{????????address?playerAddress;????????bytes32?commitment;????????Choice?choice;????}?????event?Commit(address?player);????event?Reveal(address?player,?Choice?choice);????event?Payout(address?player,?uint?amount);?????//?Initialisation?args????uint?public?bet;????uint?public?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;????}}
完成上述步骤之后,接下了需要结合检查功能来构建一个Commit Function. 该功能只被允许在两个提交阶段之一中运行。接着,我们可以确保其固有值随交易一同发送,并且将任何额外资金退回。在完成检查后,我们可以将承诺存储起来,发出Commit event,并进入下一个阶段。
具体操作如下:
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?than?commit?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);?????//?Emit?the?commit?event????emit?Commit(msg.sender);?????//?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;}
?接着,从检查这步转到揭示功能。此功能仅在揭示阶段之一中运行,且只接受有效选择。然后,找到玩家数据,以便于检查他们承诺的哈希值,确定是否有效。如果该哈希值有效,对其进行存储,触发Reveal(揭示)事件,进入下一个阶段。
?具体操作如下:
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;?????//?Emit?reveal?event????emit?Reveal(msg.sender,?commitChoice.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;}
?最后需要的是分配功能。我们需要在确定获胜者之后将奖励支付给他们。此功能也同之前每一步一样,只能在分发阶段或者揭示阶段完成之后才能运行。通过对结果进行检查,判定出获胜的一方,之后计算奖励,触发Payout event,将资金发送到胜者的地址,并为下一次游戏将状态重置。
具体操作如下:
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?yet?distribute");?????//?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?and?bets????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;}
?接下来提供一个完整的合约内容供大家参考:
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。