首先是为游戏设计电路,它执行以下列出的游戏规则:
游戏板为 10*10
网格;
网格上有五艘船,水平或垂直。它们应该具有以下大小:
船队在比赛开始前摆放好,在比赛期间就不能更改;
开火坐标由 (x, y) 表示,必须在网格内,可能击中或未击中舰队;
接下来我们确定电路的输入和输出。
(pos_x, pos_y, direction)
的元组指示每艘船在网格上的位置;(pos_x, pos_y)
;如上所述,我们可以使用 (pos_x, pos_y, orientation)
的元组来表示一艘船在网格中的位置,但是我们如何表示整个舰队的位置状态呢?
请注意,位置的有效值在 0
到 9
之间,方向值只是 0
或 1
。所以我们可以将它们中的每一个编码为 4
个比特位,并将它们全部连接起来形成一个可以表示整个元组的数字。例如:
shipState = shipX + shipY * (1<<4) + shipOrientation * (1<<8);
用同样的想法,我们可以在一个 field
中表示整个舰队的位置状态:
fleetState = carrierState + battleshipState * (1<<12) + cruiserState * (1<<24) + submarineState * (1<<36) + destroyerState * (1<<48);
接下来是计算舰队状态的哈希值。我们选择了一个 zkSNARK 友好的哈希算法 mimc7
来完成这项工作,因为它能够减少最终电路的大小。这里还可以使用其他哈希算法。
我们只是确保它与公共输入中声明的相同:
assert(mimc7::<91>(shipState, 0) == shipHash);
首先我们检查目标位置是否有效:
assert(targetX >= 0 && targetX <= 9 && targetY >= 0 && targetY <= 9);
然后我们可以将命中或未命中逻辑放在帮助函数中并在主函数中调用它:
def isShipHit(field x, field y, field o, field size, field targetX, field targetY) -> bool { return ((o == 0 && targetX == x && targetY >= y && targetY <= y + size - 1) || (o == 1 && targetY == y && targetX >= x && targetX <= x + size - 1)); }
最后电路输出是否有任意船只被击中:
return (isCarrierHit || isBattleshipHit || isCruiserHit || isSubmarineHit || isDestroyerHit);
最后我们得到完整的电路代码如右边所示。
如果你仔细看我们刚刚完成的代码,你可能会发现我们没有检查舰队位置的有效性。这意味着一个人可以通过在开始时提供一个无效的船位置作为秘密输入来欺骗它的对手,因此船舰永远不会被击中。你能更新电路以防止这种情况发生吗?试着看看我们提供的答案。
事实上,电路是基于 this repo 中的代码创建的,我们对此进行了一些更改。