|
| 1 | +# Reorgs handling |
| 2 | + |
| 3 | +Since ACL events are propagated from the FHEVM host chain to the [Gateway](../../protocol/architecture/gateway.md) immediately after being included in a block, dApp developers must take special care when encrypted information is critically important. For example, if an encrypted handle conceals the private key of a Bitcoin wallet holding significant funds, we need to ensure that this information cannot inadvertently leak to the wrong person due to a reorg on the FHEVM host chain. Therefore, it's the responsibility of dApp developers to prevent such scenarios by implementing a two-step ACL authorization process with a timelock between the request and the ACL call. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Simple example: Handling reorg risk on Ethereum |
| 8 | + |
| 9 | +On Ethereum, a reorg can be up to 95 slots deep in the worst case, so waiting for more than 95 blocks should ensure that a previously sent transaction has been finalized—unless more than 1/3 of the nodes are malicious and willing to lose their stake, which is highly improbable. |
| 10 | + |
| 11 | +❌ **Instead of writing this contract:** |
| 12 | + |
| 13 | +```solidity |
| 14 | +contract PrivateKeySale { |
| 15 | + euint256 privateKey; |
| 16 | + bool isAlreadyBought = false; |
| 17 | + |
| 18 | + constructor(externalEuint256 _privateKey, bytes inputProof) { |
| 19 | + privateKey = FHE.fromExternal(_privateKey, inputProof); |
| 20 | + FHE.allowThis(privateKey); |
| 21 | + } |
| 22 | +
|
| 23 | + function buyPrivateKey() external payable { |
| 24 | + require(msg.value == 1 ether, "Must pay 1 ETH"); |
| 25 | + require(!isBought, "Private key already bought"); |
| 26 | + isBought = true; |
| 27 | + FHE.allow(encryptedPrivateKey, msg.sender); |
| 28 | + } |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Since the `privateKey`` encrypted variable contains critical information, we don't want to mistakenly leak it for free if a reorg occurs. This could happen in the previous example because we immediately grant authorization to the buyer in the same transaction that processes the sale. |
| 33 | + |
| 34 | +✅ **We recommend writing something like this instead:** |
| 35 | + |
| 36 | +```solidity |
| 37 | +contract PrivateKeySale { |
| 38 | + euint256 privateKey; |
| 39 | + bool isAlreadyBought = false; |
| 40 | + uint256 blockWhenBought = 0; |
| 41 | + address buyer; |
| 42 | + |
| 43 | + constructor(externalEuint256 _privateKey, bytes inputProof) { |
| 44 | + privateKey = FHE.fromExternal(_privateKey, inputProof); |
| 45 | + FHE.allowThis(privateKey); |
| 46 | + } |
| 47 | +
|
| 48 | + function buyPrivateKey() external payable { |
| 49 | + require(msg.value == 1 ether, "Must pay 1 ETH"); |
| 50 | + require(!isBought, "Private key already bought"); |
| 51 | + isBought = true; |
| 52 | + blockWhenBought = block.number; |
| 53 | + buyer = msg.sender; |
| 54 | + } |
| 55 | +
|
| 56 | + function requestACL() external { |
| 57 | + require(isBought, "Private key has not been bought yet"); |
| 58 | + require(block.number > blockWhenBought+95, "Too early to request ACL, risk of reorg"); |
| 59 | + FHE.allow(privateKey, buyer); |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +This approach ensures that at least 96 blocks have elapsed between the transaction that purchases the private key and the transaction that authorizes the buyer to decrypt it. |
| 65 | + |
| 66 | +*Note:* This type of contract worsens the user experience by adding a timelock before users can decrypt data, so it should be used sparingly: only when leaked information could be critically important and high-value. |
0 commit comments