How to validate Ethereum addresses in PHP
Asked Answered
A

2

7

I'm using PHP and curl with json to interact with my geth server.

I'm able to do all I want except one thing: checking if user's inputted address is valid according to ethereum wallet format.

I saw a javascript function here, but I'm mostly using PHP, I'm not into JS at all.

Any ideas how to validate ethereum addresses in PHP?

Accountable answered 8/7, 2017 at 20:21 Comment(2)
Also, read the general algorithm (how is it checked generally) and write it in PHPBullen
Can you provide any constructive answer about ethereum/regex validation in PHP or you just felt here by hazard?Accountable
C
18

Here's a PHP implementation for Ethereum address validation against the EIP 55 specification. For details of how it works, please go through the comments.

<?php

use kornrunner\Keccak; // composer require greensea/keccak

class EthereumValidator
{
    public function isAddress(string $address): bool
    {
        // See: https://github.com/ethereum/web3.js/blob/7935e5f/lib/utils/utils.js#L415
        if ($this->matchesPattern($address)) {
            return $this->isAllSameCaps($address) ?: $this->isValidChecksum($address);
        }

        return false;
    }

    protected function matchesPattern(string $address): int
    {
        return preg_match('/^(0x)?[0-9a-f]{40}$/i', $address);
    }

    protected function isAllSameCaps(string $address): bool
    {
        return preg_match('/^(0x)?[0-9a-f]{40}$/', $address) || preg_match('/^(0x)?[0-9A-F]{40}$/', $address);
    }

    protected function isValidChecksum($address)
    {
        $address = str_replace('0x', '', $address);
        $hash = Keccak::hash(strtolower($address), 256);

        // See: https://github.com/web3j/web3j/pull/134/files#diff-db8702981afff54d3de6a913f13b7be4R42
        for ($i = 0; $i < 40; $i++ ) {
            if (ctype_alpha($address{$i})) {
                // Each uppercase letter should correlate with a first bit of 1 in the hash char with the same index,
                // and each lowercase letter with a 0 bit.
                $charInt = intval($hash{$i}, 16);

                if ((ctype_upper($address{$i}) && $charInt <= 7) || (ctype_lower($address{$i}) && $charInt > 7)) {
                    return false;
                }
            }
        }

        return true;
    }
}

Dependencies

To validate checksum addresses, we need a keccak-256 implementation in place which is not supported by the built-in hash() function. You need to require the greensea/keccak composer package as a dependency.


Kudos to @WebSpanner for pointing out the issue with SHA3 hashing.

Conferee answered 17/5, 2018 at 19:42 Comment(7)
I'm now using web3.js directly in my dapp. No php woth ethereum they don't go well together lol. Thanks for your answerAccountable
@Conferee This implementation is correct algorithmically, but the hashing algorithm used (sha3-256) is not correct according to the spec. The correct hashing algorithm is keccack-256, which is essentially the same hashing algorithm which differs only in a padding constant but therefore produces different results. Replace the Sha3 class here with the Keccak class from the greensea/keccak package to get the desired result. I've refrained from adding a new answer because I think sepehr should edit this one as I'd only be using his code except for this minor change.Pothouse
Further reading: github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md github.com/status-im/nim-keccak-tiny/issues/1Pothouse
Sha3 generation is not correct here! Example: Address: 0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359, hash: 9abe8ec2cc3d024b8cec105fd0c6da114c2b6f3413e0f080242c304024f5f7ec, I have try Node.js code for this address, it returns different hash: d5272d0fe053f9072ef335d27ac67d481da35108f4e59711486ae6cff4a16ee5Precast
Your answer was extremely helpful, and so is ethereum-address-validator which can also return the canonical address.Celindaceline
You don't need to use regex in isAllSameCaps since the format is already checked in matchesPattern. Instead you could just test that the address is equal to the lower/upper case version of it: return strtolower($value) === $value || strtoupper($value) === $value;Raeleneraf
That makes perfect sense @ub3rst4r. Please feel free to edit the answer, if you like.Conferee
Z
6

Basically, you can convert the javascript entirely to PHP. Here i have been able to convert and test the code for validating an ethereum address in PHP.

/**
 * Checks if the given string is an address
 *
 * @method isAddress
 * @param {String} $address the given HEX adress
 * @return {Boolean}
*/
function isAddress($address) {
    if (!preg_match('/^(0x)?[0-9a-f]{40}$/i',$address)) {
        // check if it has the basic requirements of an address
        return false;
    } elseif (!preg_match('/^(0x)?[0-9a-f]{40}$/',$address) || preg_match('/^(0x)?[0-9A-F]{40}$/',$address)) {
        // If it's all small caps or all all caps, return true
        return true;
    } else {
        // Otherwise check each case
        return isChecksumAddress($address);
    }
}

/**
 * Checks if the given string is a checksummed address
 *
 * @method isChecksumAddress
 * @param {String} $address the given HEX adress
 * @return {Boolean}
*/
function isChecksumAddress($address) {
    // Check each case
    $address = str_replace('0x','',$address);
    $addressHash = hash('sha3',strtolower($address));
    $addressArray=str_split($address);
    $addressHashArray=str_split($addressHash);

    for($i = 0; $i < 40; $i++ ) {
        // the nth letter should be uppercase if the nth digit of casemap is 1
        if ((intval($addressHashArray[$i], 16) > 7 && strtoupper($addressArray[$i]) !== $addressArray[$i]) || (intval($addressHashArray[$i], 16) <= 7 && strtolower($addressArray[$i]) !== $addressArray[$i])) {
            return false;
        }
    }
    return true;
}

Meanwhile, for someone looking for a very simple regular expression for checking ethereum address validity (e.g to use is as a pattern attribute of an HTML field), this regular expression may suffice.

^(0x)?[0-9a-fA-F]{40}$
Zamir answered 7/10, 2017 at 11:39 Comment(7)
Thanks ! I now use jquery and web3.js so I can use web3.isAddress() :)Accountable
Unknown hashing algorithm: sha3Pectoral
Not only non-working, but also wrong implementation and yet accepted. Amazing world we live in.Conferee
@Conferee Maybe you should clarify where exactly you need help with. FYI I actually copied pasted the code above after using it myself.Zamir
@Zamir I don't think I need help, thanks :) I wonder how could you "use" the code while hash() has no support for SHA3 and the second set of preg_match calls are actually being mistakenly negated.Conferee
There is no sha3 hashing algorythm in PHP. Use sha3-256 instead, which is available from the PHP 7.1.0Florentinoflorenza
Does not work with sha3, and it's a wrong hash with sha3-256. @sepehr's solution works...Jijib

© 2022 - 2024 — McMap. All rights reserved.