From 8d20a5acbf701bdbbe64f05a19f85ededd6d77bf Mon Sep 17 00:00:00 2001 From: James Zaki Date: Sat, 6 Nov 2021 12:47:06 +1100 Subject: [PATCH] Create BLSWallets with deterministic addresses. Closes #15 --- contracts/contracts/BLSWallet.sol | 11 ++++-- contracts/contracts/VerificationGateway.sol | 42 ++++++++++++++++----- contracts/shared/helpers/Fixture.ts | 2 +- contracts/test/walletAction-test.ts | 21 ++++++++--- 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/contracts/contracts/BLSWallet.sol b/contracts/contracts/BLSWallet.sol index f445b538..67581bb9 100644 --- a/contracts/contracts/BLSWallet.sol +++ b/contracts/contracts/BLSWallet.sol @@ -13,18 +13,23 @@ interface IVerificationGateway { function walletCrossCheck(bytes32 publicKeyHash) external; } -contract BLSWallet +contract BLSWallet is Initializable { address public gateway; uint256[4] public publicKey; uint256 public nonce; - constructor(uint256[4] memory blsKey) { + constructor() { gateway = msg.sender; - publicKey = blsKey; nonce = 0; } + function initialize( + uint256[4] memory blsKey + ) external initializer onlyGateway { + publicKey = blsKey; + } + receive() external payable {} fallback() external payable {} diff --git a/contracts/contracts/VerificationGateway.sol b/contracts/contracts/VerificationGateway.sol index 4802ac98..8819f9e3 100644 --- a/contracts/contracts/VerificationGateway.sol +++ b/contracts/contracts/VerificationGateway.sol @@ -17,8 +17,6 @@ contract VerificationGateway is Initializable uint8 constant BLS_LEN = 4; // uint256[BLS_LEN] ZERO_BLS_SIG = [uint256(0), uint256(0), uint256(0), uint256(0)]; - mapping (bytes32 => BLSWallet) public walletFromHash; - IBLS public blsLib; /** @@ -74,11 +72,35 @@ contract VerificationGateway is Initializable require(verified, "VerificationGateway: All sigs not verified"); } + function hasCode(address a) private view returns (bool) { + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(a) } + return size > 0; + } + + /** + @param hash BLS public key hash used as salt for create2 + @return BLSWallet at calculated address (if code exists), otherwise zero address + */ + function walletFromHash(bytes32 hash) public view returns (BLSWallet) { + address walletAddress = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + hash, + keccak256(type(BLSWallet).creationCode) + ))))); + if (!hasCode(walletAddress)) { + walletAddress = address(0); + } + return BLSWallet(payable(walletAddress)); + } + /** Useful no-op function to call when calling a wallet for the first time. */ function walletCrossCheck(bytes32 hash) public payable { - require(msg.sender == address(walletFromHash[hash])); + require(msg.sender == address(walletFromHash(hash))); } /** @@ -107,7 +129,7 @@ contract VerificationGateway is Initializable // construct params for signature verification publicKeyHash = keccak256(abi.encodePacked(publicKeys[i])); - wallet = walletFromHash[publicKeyHash]; + wallet = walletFromHash(publicKeyHash); if (txs[i].nonce == wallet.nonce()) { // action transaction (increments nonce) @@ -132,12 +154,14 @@ contract VerificationGateway is Initializable private // consider making external and VG stateless { bytes32 publicKeyHash = keccak256(abi.encodePacked(publicKey)); - // wallet at publicKeyHash doesn't exist - if (address(walletFromHash[publicKeyHash]) == address(0)) { - // blsKeysFromHash[publicKeyHash] = publicKey; - walletFromHash[publicKeyHash] = new BLSWallet(publicKey); + BLSWallet blsWallet = walletFromHash(publicKeyHash); + + // wallet with publicKeyHash doesn't exist at expected create2 address + if (address(blsWallet) == address(0)) { + blsWallet = new BLSWallet{salt: publicKeyHash}(); + blsWallet.initialize(publicKey); emit WalletCreated( - address(walletFromHash[publicKeyHash]), + address(blsWallet), publicKey ); } diff --git a/contracts/shared/helpers/Fixture.ts b/contracts/shared/helpers/Fixture.ts index a440a2c4..6025bf88 100644 --- a/contracts/shared/helpers/Fixture.ts +++ b/contracts/shared/helpers/Fixture.ts @@ -13,7 +13,7 @@ import blsSignFunction from "./blsSignFunction"; import blsKeyHash from "./blsKeyHash"; import { exit, send } from "process"; import Create2Fixture from "./Create2Fixture"; -import { BLSExpander, BLSWallet__factory, VerificationGateway } from "../../typechain"; +import { BLSExpander, BLSWallet, BLSWallet__factory, VerificationGateway } from "../../typechain"; const DOMAIN_HEX = utils.keccak256("0xfeedbee5"); const DOMAIN = arrayify(DOMAIN_HEX); diff --git a/contracts/test/walletAction-test.ts b/contracts/test/walletAction-test.ts index b279705a..112dc0ac 100644 --- a/contracts/test/walletAction-test.ts +++ b/contracts/test/walletAction-test.ts @@ -1,4 +1,4 @@ -import { expect, assert } from "chai"; +import { expect } from "chai"; import expectRevert from "../shared/helpers/expectRevert"; import { ethers, network } from "hardhat"; @@ -12,10 +12,10 @@ import { aggregate } from "../shared/lib/hubble-bls/src/signer"; import { BigNumber } from "ethers"; import blsKeyHash from "../shared/helpers/blsKeyHash"; import blsSignFunction from "../shared/helpers/blsSignFunction"; -import { formatUnits, parseEther } from "@ethersproject/units"; +import { parseEther } from "@ethersproject/units"; import deployAndRunPrecompileCostEstimator from "../shared/helpers/deployAndRunPrecompileCostEstimator"; import getDeployedAddresses from "../shared/helpers/getDeployedAddresses"; -import deployerContract, { defaultDeployerAddress } from "../shared/helpers/deployDeployer"; +import { defaultDeployerAddress } from "../shared/helpers/deployDeployer"; describe('WalletActions', async function () { @@ -62,15 +62,24 @@ describe('WalletActions', async function () { let blsSigner = fx.blsSigners[0]; let walletAddress = await fx.createBLSWallet(blsSigner); + const BLSWallet = await ethers.getContractFactory("BLSWallet"); + + let calculatedAddress = ethers.utils.getCreate2Address( + fx.verificationGateway.address, + blsKeyHash(blsSigner), + ethers.utils.solidityKeccak256( + ["bytes"], + [BLSWallet.bytecode] + ) + ); + expect(calculatedAddress).to.equal(walletAddress); + let blsWallet = fx.BLSWallet.attach(walletAddress); await Promise.all(blsSigner.pubkey.map(async (keyPart, i) => expect(await blsWallet.publicKey(i)) .to.equal(keyPart))); - // Check revert when adding same wallet twice - // await expectRevert(fx.createBLSWallet(blsSigner)); - }); it('should receive ETH', async function() {