How to validate an Ethereum address PHP / Laravel? - php

How can I check if the Ethereum address from Laravel input is valid in terms of format?

Here's a Laravel custom validation rule for validating Ethereum addresses against the EIP 55 specification. For details of how it works, please go through the comments.
<?php
namespace App\Rules;
use kornrunner\Keccak; // composer require greensea/keccak
use Illuminate\Contracts\Validation\Rule;
class ValidEthereumAddress implements Rule
{
/**
* #var Keccak
*/
protected $hasher;
public function __construct(Keccak $hasher)
{
$this->keccak = $hasher;
}
public function passes($attribute, $value)
{
// See: https://github.com/ethereum/web3.js/blob/7935e5f/lib/utils/utils.js#L415
if ($this->matchesPattern($value)) {
return $this->isAllSameCaps($value) ?: $this->isValidChecksum($value);
}
return false;
}
public function message()
{
return 'The :attribute must be a valid Ethereum address.';
}
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 = $this->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 Keccac implementation in place which is not supported by the built-in hash() function. You need to require this pure PHP implementation for the above rule to work.

Any 42 character string starting with 0x, and following with 0-9, A-F, a-f (valid hex characters) represent a valid Ethereum address.
You can find more information about lowercase and partial uppercase (for adding checksum) Ethereum addresses format here.

As of Laravel 9, If you are using a request class for validation, add this to the validation rules:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'address' => 'regex:/^(0x)?(?i:[0-9a-f]){40}$/'
];
}
Notes:
Here, the input field name is address
I applied case insensitivity to only a part of the regular expression using the format (?i: ... )

Related

preg_match(): No ending delimiter '/' found Laravel

I'm doing a validation by regex from a request, this validation is to check that the parameter that is sent is an IP.
My rules are as follows:
<?php
namespace App\Http\Requests\usuarios;
use Illuminate\Foundation\Http\FormRequest;
class storeVPN extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'segmentwan' => 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/',
'segmentlan' => 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/',
];
}
the debugger shows me that the error is in this line
the debugger shows me that the error is in this line
return preg_match($parameters[0], $value) > 0;
y el error completo es el siguiente
* #return bool
*/
public function validateRegex($attribute, $value, $parameters)
{
if (! is_string($value) && ! is_numeric($value)) {
return false;
}
$this->requireParameterCount(1, $parameters, 'regex');
return preg_match($parameters[0], $value) > 0;
}
/**
* Validate that a required attribute exists.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function validateRequired($attribute, $value)
{
if (is_null($value)) {
return false;
} elseif (is_string($value) && trim($value) === '') {
return false;
} elseif ((is_array($value) || $value instanceof Countable) && count($value) < 1) {
return false;
} elseif ($value instanceof File) {
return (string) $value->getPath() !== '';
}
In Laravel you have ip validation rule so you can use:
return [
'segmentwan' => 'ip',
'segmentlan' => 'ip',
];
Also to be honest when I'm looking at your code I don't see the error. Are you sure error is with those 2 regexes?
Change your Validation rules. When you are using regex you need to use Array instead of pipe delimiter.
return [
'segmentwan' => [ 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/'],
'segmentlan' => [ 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/'],
];
Note: When using the regex pattern, it may be necessary to specify
rules in an array instead of using pipe delimiters, especially if the
regular expression contains a pipe character.
UPDATE
If you are validating IP address than laravel provides three validation options.
ip:
The field under validation must be an IP address.
ipv4:
The field under validation must be an IPv4 address.
ipv6:
The field under validation must be an IPv6 address.
You shouldn't be using regex to validate IPs; you can use filter_var() built-in function instead
It has many flags including FILTER_FLAG_IPV4 for ipv4 & FILTER_FLAG_IPV6 for ipv6 ... Ofcourse you can mix flags so you are able to check both

How to validate Ethereum addresses in PHP

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?
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.
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}$

How to match part of string inside array - PHP

Is there a way to match a part of a string within an array in PHP?
I would like to validate a user ip against allowed IPs. Therefore I have created an array with IPs and coresponding partner_id. This works, however I also want to allow an entire subnet and would therefore need to mach against part of the array. Is this possible?
This is my code:
# define partner IPs
$partner_ips = array(
'192.168.56.1' => 0, // dev
'192.168.57.*' => 1 // office ips
);
# test for partner IP and associate partner_id if found
if (array_key_exists($_SERVER['REMOTE_ADDR'], $partner_ips))
$partner_id = $partner_ips[$_SERVER['REMOTE_ADDR']];
else
$partner_id = false;
Thank you for any help on this.
Check the ip format first. Build two different arrays, one for full ip adresses and one for subnets. An example class (feel free to make it PSR-2 compliant, since you use PHP 5.6 you can also declare the two arrays as class constants instead of static variables):
class RemoteAddress {
private $ip;
private $id;
private static $partners_ips = [
'192.168.56.1' => 0,
'192.168.58.4' => 2,
'192.168.59.2' => 3 ];
private static $partners_subnets = [ // note that subnets must end with a dot
'192.168.57.' => 1,
'192.168.60.' => 4,
'192.168.61.' => 5 ];
public function __construct($ip) {
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
throw new Exception("invalid IP address");
$this->ip = $ip;
$this->id = $this->searchID();
}
public function getIDPartner() {
return $this->id;
}
private function searchID() {
if (array_key_exists($this->ip, self::$partners_ips))
return self::$partners_ips[$this->ip];
foreach (self::$partners_subnets as $subnet => $id) {
if (strpos($this->ip, $subnet) === 0)
return $id;
}
return false;
}
}
You can use it like this:
try {
if (isset($_SERVER['REMOTE_ADDR'])) {
$remAddr = new RemoteAddress($_SERVER['REMOTE_ADDR']);
var_dump($remAddr->getIDPartner());
} else throw new Exception('$_SERVER[\'REMOTE_ADDR\'] is not defined');
} catch(Exception $e) {
echo $e->getMessage();
}
You can use in_array for check if your string exist in array or not
http://php.net/manual/en/function.in-array.php

How to generate unique random value for each user in laravel and add it to database

I am developing a event organization website. Here when the user registers for an event he will be given a unique random number(10 digit), which we use to generate a barcode and mail it to him. Now,
I want to make the number unique for each registered event.
And also random
One solution is to grab all the random numbers in an array and generate a random number using Php rand(1000000000, 9999999999) and loop through and check all the values. Grab the first value that doesn't equal to any of the values in the array and add it to the database.
But I am thinking that there might be a better solution to this. Any suggestion?
You can use php's uniqid() function to generate a unique ID based on the microtime (current time in microseconds)
Example:
<?php
echo uniqid();
?>
Output:
56c3096338cdb
Your logic isn't technically faulty. However, if your application attracts lots of users, fetching all of the random numbers may well become unnecessarily expensive, in terms of resources and computation time.
I would suggest another approach, where you generate a random number and then check it against the database.
function generateBarcodeNumber() {
$number = mt_rand(1000000000, 9999999999); // better than rand()
// call the same function if the barcode exists already
if (barcodeNumberExists($number)) {
return generateBarcodeNumber();
}
// otherwise, it's valid and can be used
return $number;
}
function barcodeNumberExists($number) {
// query the database and return a boolean
// for instance, it might look like this in Laravel
return User::whereBarcodeNumber($number)->exists();
}
This is good:
do {
$refrence_id = mt_rand( 1000000000, 9999999999 );
} while ( DB::table( 'transations' )->where( 'RefrenceID', $refrence_id )->exists() );
To avoid the problem of having to check to see if a matching code exists every time a new one is created, I just catch MySQL's duplicate record exception (error code 1062). If that error is caught, I just call the function again until the save is successful. That way, it only has to generate a new code if it collides with an existing one. Runs a lot faster -- but obviously gets a bit slower as your number of users approaches the number of possible barcodes.
function generateBarcode($user_id) {
try {
$user = User::find($user_id);
$user->barcode = mt_rand(1000000000, 9999999999);
$user->save();
} catch (Exception $e) {
$error_info = $e->errorInfo;
if($error_info[1] == 1062) {
generateBarcode($user_id);
} else {
// Only logs when an error other than duplicate happens
Log::error($e);
}
}
}
So just loop through all the users you want to assign a code to:
foreach(User::all() as $user) {
generateBarcode($user->id);
}
You could also add some logic to escape the function loop if a maximum number of attempts are made, but I've never bothered because collisions are unlikely.
Looping through the array won't be that efficient. If your database becomes too large then it slow down the entire process and also there might be a rare situation when 2 threads are looping through the array for the same random number and it will be found available and return same number to both the tickets.
So instead of looping through the array you can set the 10 digit registration id as primary key and instead of looping through the array you can insert the registration details along with randomly generated number, if the database insert operation is successful you can return the registration id but if not then regenerate the random number and insert.
Alternate solution which will be more effective
Instead of 10 digit random numbers you can use timestamp to generate a 10 digit unique registration number and to make it random you can randomize the first 2 or 3 digits of the timestamp
One Solution could be like this:
use Illuminate\Support\Facades\Validator;
private function genUserCode(){
$this->user_code = [
'user_code' => mt_rand(1000000000,9999999999)
];
$rules = ['user_code' => 'unique:users'];
$validate = Validator::make($this->user_code, $rules)->passes();
return $validate ? $this->user_code['user_code'] : $this->genUserCode();
}
Its generating a random number between 1000000000 and 9999999999. After that, it validates the number against the table. If true then it returns the number, otherwise runs the function again.
I made something like this
/**
* Generate unique shipment ID
*
* #param int $length
*
* #return string
*/
function generateShipmentId($length)
{
$number = '';
do {
for ($i=$length; $i--; $i>0) {
$number .= mt_rand(0,9);
}
} while ( !empty(DB::table('shipments')->where('id', $number)->first(['id'])) );
return $number;
}
<?php
declare(strict_types=1);
namespace App\Helpers;
use App\Exceptions\GeneratorException;
class GeneratorHelper
{
public static $limitIterations = 100000;
/**
* #param string $column
* #param string $modelClass
* #return string
* #throws GeneratorException
*/
public static function generateID(string $modelClass, string $column): string
{
return self::run(
$modelClass,
$column,
self::IDGenerator(),
'Generation id is failed. The loop limit exceeds ' . self::$limitIterations
);
}
/**
* #param string $modelClass
* #param string $column
* #param \Generator $generator
* #param string $exceptionMessage
* #param array $whereParams
* #return string
* #throws GeneratorException
*/
protected static function run(string $modelClass, string $column, \Generator $generator, string $exceptionMessage, array $whereParams = []): string
{
try {
foreach ($generator as $id) {
$query = $modelClass::where([$column => $id]);
foreach ($whereParams as $param) {
$query->where(...$param);
}
if (!$query->first()) {
return $id;
}
}
} catch (\Throwable $e) {
$exceptionMessage = $e->getMessage();
}
throw new GeneratorException($exceptionMessage);
}
protected static function IDGenerator(): ?\Generator
{
for ($i = 1; $i <= self::$limitIterations; $i++) {
yield (string)random_int(1000000000, 9999999999);
}
return null;
}
}
sample usage
$card->number = GeneratorHelper::generateID(Card::class, 'number');
for me, I prefer using MySQL way, because when you have a large amount of data in your DB, you will have too much quires to check your number uniqueness,
for example, a code like this:
do {
$code = random_int(100000, 99999999);
}
while (AgentOrder::where("order_number", "=", $code)->exists());
so , this "do while" loop would be excueted too many times.
to avoid that, you can use MySQL way like:
private function getUniqueCodeNumber()
{
$value = AgentOrder::query()->selectRaw('FLOOR(1000000 + RAND() * 10000000) AS generatedCode')
->whereRaw("'generatedCode' NOT IN (SELECT order_number FROM agent_orders WHERE agent_orders.order_number IS NOT NULL)")
->limit(1)->first();
if ($value == null) return 100000000;
$value = (int)$value->generatedCode;
return $value;
}
this answer is inspired from this answer.
Helper (app/Helpers/Constants.php)
<?php
namespace App\Helper;
class Constants
{
public static function getUniqueId($model){
$id = mt_rand(1000000000, 9999999999);
if($model->find($id))
return self::getUniqueId($model);
return $id;
}
}
?>
Model (app/Models/User.php)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public static function boot(){
parent::boot();
$creationCallback = function ($model) {
if (empty($model->{$model->getKeyName()}))
$model->{$model->getKeyName()} = Constants::getUniqueId(new self());
};
static::creating($creationCallback);
}
}
?>
Explanation: Instead of calling the getUniqueId method in every controller. You can write it inside model.
From the #Joel Hinz answer :
public function set_number() {
$number = mt_rand(1000000000, 9999999999);
return User::where('number', $number)->exists() ? $this->set_number() : $number;
}
Hope that helped.

How to get the real type of a value inside string?

I was searching here on StackOverflow about converting string to the real value and i didn't found.
I need a function like "gettype" that does something like the result above, but i can't do it all :s
gettypefromstring("1.234"); //returns (doble)1,234;
gettypefromstring("1234"); //returns (int)1234;
gettypefromstring("a"); //returns (char)a;
gettypefromstring("true"); //returns (bool)true;
gettypefromstring("khtdf"); //returns (string)"khtdf";
Thanks to all :)
1+ for Svisstack! ;)
Here is the function if someone want it:
function gettype_fromstring($string){
// (c) José Moreira - Microdual (www.microdual.com)
return gettype(getcorrectvariable($string));
}
function getcorrectvariable($string){
// (c) José Moreira - Microdual (www.microdual.com)
// With the help of Svisstack (http://stackoverflow.com/users/283564/svisstack)
/* FUNCTION FLOW */
// *1. Remove unused spaces
// *2. Check if it is empty, if yes, return blank string
// *3. Check if it is numeric
// *4. If numeric, this may be a integer or double, must compare this values.
// *5. If string, try parse to bool.
// *6. If not, this is string.
$string=trim($string);
if(empty($string)) return "";
if(!preg_match("/[^0-9.]+/",$string)){
if(preg_match("/[.]+/",$string)){
return (double)$string;
}else{
return (int)$string;
}
}
if($string=="true") return true;
if($string=="false") return false;
return (string)$string;
}
I used this function to know if the number X is multiple of Y.
Example:
$number=6;
$multipleof=2;
if(gettype($number/$multipleof)=="integer") echo "The number ".$number." is multiple of ".$multipleoff.".";
But the framework that i work returns always the input vars as strings.
You must try to convert it in specified order:
Check is double
If double, this may be a integer, you must convert and compare this values.
If not, this is char if lenght is == 1.
If not, this is string.
If string, try parse to bool.
You can't use gettype because you may get string type of decimal writed in string.
Here is an updated version of this 9 year old function:
/**
* Converts a form input request field's type to its proper type after values are received stringified.
*
* Function flow:
* 1. Check if it is an array, if yes, return array
* 2. Remove unused spaces
* 3. Check if it is '0', if yes, return 0
* 4. Check if it is empty, if yes, return blank string
* 5. Check if it is 'null', if yes, return null
* 6. Check if it is 'undefined', if yes, return null
* 7. Check if it is '1', if yes, return 1
* 8. Check if it is numeric
* 9. If numeric, this may be a integer or double, must compare this values
* 10. If string, try parse to bool
* 11. If not, this is string
*
* (c) José Moreira - Microdual (www.microdual.com)
* With the help of Svisstack (http://stackoverflow.com/users/283564/svisstack)
*
* Found at: https://stackoverflow.com/questions/2690654/how-to-get-the-real-type-of-a-value-inside-string
*
* #param string $string
* #return mixed
*/
function typeCorrected($string) {
if (gettype($string) === 'array') {
return (array)$string;
}
$string = trim($string);
if ($string === '0') { // we must check this before empty because zero is empty
return 0;
}
if (empty($string)) {
return '';
}
if ($string === 'null') {
return null;
}
if ($string === 'undefined') {
return null;
}
if ($string === '1') {
return 1;
}
if (!preg_match('/[^0-9.]+/', $string)) {
if(preg_match('/[.]+/', $string)) {
return (double)$string;
}else{
return (int)$string;
}
}
if ($string == 'true') {
return true;
}
if ($string == 'false') {
return false;
}
return (string)$string;
}
I am using it in a Laravel middleware to transform form values that were stringified by browser JavaScript's FormData.append() back into their correct PHP types:
public function handle($request, Closure $next)
{
$input = $request->all();
foreach($input as $key => $value) {
$input[$key] = $this->typeCorrected($value);
}
$request->replace($input);
return $next($request);
}
To create that, type in your CLI php artisan make:middleware TransformPayloadTypes.
Then paste in the above handle function.
Don't forget to paste in the typeCorrected function also. I currently recommend making it a private function in your middleware class, but I don't claim to be a super-expert.
You can imagine that $request->all() is an array of key/value pairs, and it comes in with all values stringified, so the goal is to convert them back to their true type. The typeCorrected function does this. I've been running it in an application for a few weeks now, so edge cases could remain, but in practice, it is working as intended.
If you get the above working, you should be able to do something like this in Axios:
// note: `route()` is from Tightenco Ziggy composer package
const post = await axios.post(route('admin.examples.create', {
...this.example,
category: undefined,
category_id: this.example.category.id,
}));
Then, in your Laravel controller, you can do \Log::debug($request->all()); and see something like this:
[2020-10-12 17:52:43] local.DEBUG: array (
'status' => 1,
'slug' => 'asdf',
'name' => 'asdf',
'category_id' => 2,
)
With the key fact being that you see 'status' => 1, and not 'status' => '1',
All of this will allow you to submit JSON payloads via Axios and receive non-nested values in your FormRequest classes and controllers as the actual payload types are mutated. I found other solutions to be too complex. This above solution allows you to submit flat JSON payloads from pure JavaScript easily (so far, haha).

Categories