构建数字化“分歧终端机”——探索Commit-Reveal Scheme | 技术帖

以太坊是一个公共区块链,可因此却导致了对于隐私数据的管理困难。而有许多应用程序则必需要隐藏值才能正常运行,因此,commitment schemes就成为了一个卓绝的解决方案。

 构建数字化“分歧终端机”——探索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;
}

 完整合约内容可见公众号。

以太坊是一个公共区块链,可因此导致了对于隐私数据的管理困难。而有许多应用程序就像是上面提到的猜拳游戏一样,需要隐藏值才能正常运行,因此,commitment schemes就成为了一个卓绝的解决方案。

构建数字化“分歧终端机”——探索Commit-Reveal Scheme | 技术帖

踢马河:RaTiO Fintech合伙人,曾任某券商自营操盘手,十余年海外对冲基金和国内大型投资机构基金经理,资深交易建模专家,币圈大咖。

请尊重原创!转载请注明出处。

转载声明:本文 由CoinON抓取收录,观点仅代表作者本人,不代表CoinON资讯立场,CoinON不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。若以此作为投资依据,请自行承担全部责任。

声明:图文来源于网络,如有侵权请联系删除

风险提示:投资有风险,入市需谨慎。本资讯不作为投资理财建议。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 2020年1月20日 上午1:30
下一篇 2020年1月20日 上午1:31

相关推荐

构建数字化“分歧终端机”——探索Commit-Reveal Scheme | 技术帖

星期一 2020-01-20 1:30:47

 构建数字化“分歧终端机”——探索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;
}

 完整合约内容可见公众号。

以太坊是一个公共区块链,可因此导致了对于隐私数据的管理困难。而有许多应用程序就像是上面提到的猜拳游戏一样,需要隐藏值才能正常运行,因此,commitment schemes就成为了一个卓绝的解决方案。

构建数字化“分歧终端机”——探索Commit-Reveal Scheme | 技术帖

踢马河:RaTiO Fintech合伙人,曾任某券商自营操盘手,十余年海外对冲基金和国内大型投资机构基金经理,资深交易建模专家,币圈大咖。

请尊重原创!转载请注明出处。