2

I have a function which is accepting a string argument as input parameter.
This string is an IPFS hash and I would like to check that the input is actually just that and not a malicious link or the like.

How can I use require check to achieve this?
E.g. check that the input argument is on pair with a format such as mtYgPpHdWEz79ojWnPbdGgPpHdWEz7 and not malware.com

4

You may use the following modifier:

modifier onlyIPFSHash (string memory str) {
    bytes memory b = bytes (str);
    require (b.length > 6);
    for (uint i = 0; i < b.length; i++)
        require (0x7ffeffe07ff7dfe03fe000000000000 & (uint(1) << uint8 (b [i])) > 0);

    _;
}

like this:

function foo (string memory s) onlyIPFSHash (s) public {
    ...
}

Explanation: IPFS hash is Base58 encoding of at least 5 bytes, thus at least 7 characters. Base58 allows the following characters: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz. Binary representation of magic number 0x7ffeffe07ff7dfe03fe000000000000 has ones at positions that corresponds to the codes of characters allowed in Base58, and zeros at all other positions.

Longer explanation about magic number: Let's look at binary representation of magic number (you may use this online tool to convert hexadecimal representation into binary): 111111111111110111111111110000001111111111101111101111111100000001111111110000000000000000000000000000000000000000000000000

Traditionally, bits within a number are numbered from right to left starting from zero. Also, EVM operates with 256-bit numbers, but our number only has 123 bits, so EVM will add 133 leading zeros to it. Of course, adding leading zeros to number representation in whatever positional encoding does not affect its value:

bit #255 --+    +-- bit #122                                                                                                     bit #0 --+
           |    |                                                                                                                         |
           V    V                                                                                                                         V
           0...0111111111111110111111111110000001111111111101111101111111100000001111111110000000000000000000000000000000000000000000000000
                              ^                               ^
                              |                               |
                   bit #108 --+                               +-- bit #76

As I said in Explanation, each bit corresponds to a character code. It is set to one in case character with this code is permitted in Base85, and set to zero in case the character is not permitted. For example, character 'l' (small Latin "El") is not allowed in Base58. This character has code 108, so bit #108 is zero. Character 'L' (capital Latin "El") is permitted and its code is 76, so bit #76 is one. You may find character codes here. Note, that there are exactly 58 ones. This is because there are 58 characters allowed in Base58, hence the name.

Longer explanation about formula: uint(1) is just number 1 represented as 256-bit unsigned integer, i.e. in binary form it is 0..<253 zeros>..01. b [i] is a i-th byte from UTF-8 encoding of the string we are checking. uint8 (b [i]) is this byte converted into 8-bit unsigned integer. For ASCII characters this will be character code, and for non-ASCII Unicode characters this will be something between 128 and 255 inclusive. See UTF-8 description for details. uint(1) << uint8 (b [i]) is a binary representation of 1 shifted left by character code, i.e. if character code is 108, the result in binary representation will be 0...010...0 where the only "one" is at position 108. Operation & is so called bitwise "AND". It outputs number whose binary representation has ones only in those positions, where binary representations of both arguments have ones. As long as right argument has one only in one position, the result will be the same as right argument in case left argument has "one" at this positions, and will be zero in case left argument has zero at this position. Left argument is out magic number which has ones only in those positions that correspond to valid Base58 characters, so returned value will be zero in case current char is no permitted, and value will be the same as right argument (which is always greater than zero), in case character is permitted. So we compare result to zero to know whether character is permitted in Base46, and we require this comparison to return true.

  • can you elaborate a little more on the magic number :) What is 0x7ffefeffe07ff7dfe03fe portion of it? that is 1's in base58 format? – NowsyMe Mar 29 '19 at 18:48
  • In code magic number is in hexadecimal format, not in Base58. – Mikhail Vladimirov Mar 29 '19 at 19:05
  • I see, but I don't have a magic number hardcoded, I don't know the hash before it is inputted into the function as an argument? also what does this check (uint(1) << uint8 (b [i])) > 0)? we shift to the left to replace the zeroes? – NowsyMe Mar 30 '19 at 1:03
  • 1
    Added longer explanations for non-developers – Mikhail Vladimirov Mar 30 '19 at 5:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.