Search code examples
ethereumsoliditysmartcontractsmerkle-treekeccak

How to concatenate 2 String in Solidity and encode Keccak256?


i'm working at a smart-contract that has a merkle-tree function in it.

Basically i'm passing the proof from a firebase function that take in input the address and the n° of token reserved by the user, and gives as output the proof to the front-end that execute the smart-contract function.

The only thing i need to figure out is the leaf calculation, on the firebase function it is composed by encoding keccak256 the string in the array, composed by an eth address concatenated with the number of reserved tokens (0x38207d0e84edb04a57482c3769fe192617520373 + 3), in the smart contract instead, the leaf is calculated with the keccak256(abi.encodepacked(0x38207d0e84edb04a57482c3769fe1926175203733, 3), and it's not passing the verifiy...

I think these two methods of calculation are different in some part, can you help me?

I'm going to paste the code of both functions:

SOLIDITY


// 1. Address check, if the proof is valid

function isPrivateListed(
        uint256 _addressReservedTokens,
        bytes32[] memory _proof
    ) public view returns (bool) {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _addressReservedTokens);
        return MerkleProof.verify(_proof, privateListRoot, leaf);
    }



// 2. Mint function called from the front-end

    function privateMint(
        uint256 _mintAmount,
        uint256 _addressReservedTokens,
        bytes32[] calldata proof
    ) public payable {
        require(privateOpen, "The private mint is not opened");
        require(
            isPrivateListed(_addressReservedTokens, proof),
            "you are not in the private list"
        );
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

        uint256 owned = addressMintedBalance[msg.sender];
        require(
            owned + _mintAmount <= _addressReservedTokens,
            "You have less nft reserved"
        );

        for (uint256 i = 1; i <= _mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
            addressMintedBalance[msg.sender]++;
        }
    }

FIREBASE FUNCTION

// 1. Get parameters from the function called from client side

  const walletAddress = data.walletAddress;

  let whitelistAddresses = [
    '0x38207d0e84edb04a57482c3769fe1926175203733',
    '0x943926a8ff0000350d0b879a658fa52bcd4fca186',
  ];

  // 2. Create a new array of `leafNodes` by hashing all indexes of the `whitelistAddresses`
  // using `keccak256`. Then creates a Merkle Tree object using keccak256 as the algorithm.
  const leafNodes = whitelistAddresses.map((addr) => keccak256(addr));
  const merkleTree = new MerkleTree(leafNodes, keccak256, {
    sortPairs: true,
  });

  // 3. Get root hash of the `merkleeTree` in hexadecimal format (0x)
  const rootHash = merkleTree.getRoot();
  const rootHashHex = merkleTree.getHexRoot();
  // ***** ***** ***** CLIENT-SIDE ***** ***** ***** //

  const claimingAddress = keccak256(walletAddress);
  const hexProof = merkleTree.getHexProof(claimingAddress);

  const verifyProof = merkleTree.verify(hexProof, claimingAddress, rootHash);

Solution

  • In solidity you're hashing a (20+32)-bytes element

    bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _addressReservedTokens);
    

    Since msg.sender is 20 bytes and _addressReservedTokens is 32 bytes, the output of abi.encodePacked() is something like this:

    0x38207d0e84edb04a57482c3769fe1926175203730000000000000000000000000000000000000000000000000000000000000003
    

    This is clearly different than 0x38207d0e84edb04a57482c3769fe1926175203733 used by firebase.

    Also I'm not sure if the js keccak256 is interpreting that value as string or as bytes (in solidity it's bytes).

    The best fix is probably changing the js code. You can write the correct whitelistAddresses using ethers, see here https://ethereum.stackexchange.com/questions/119990/how-to-mimic-abi-encodepacked-in-ethers