Skip to content

Commit 710e42b

Browse files
author
Roman Bylbas
authored
Merge pull request #7 from make-software/checksum
Checksum
2 parents 185b50e + e3818c3 commit 710e42b

5 files changed

Lines changed: 167 additions & 62 deletions

File tree

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"ext-sodium": "*",
1414
"ext-gmp": "*",
1515
"ext-mbstring": "*",
16-
"deemru/blake2b": "^1.0",
1716
"mdanter/ecc": "^1.0",
1817
"ionux/phactor": "1.0.8"
1918
},

composer.lock

Lines changed: 1 addition & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Util/CEP57ChecksumUtil.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
namespace Casper\Util;
4+
5+
class CEP57ChecksumUtil
6+
{
7+
private const BLAKE2B_HASH_LENGTH = 64;
8+
private const HEX_CHARS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
9+
10+
/**
11+
* @param string $hex
12+
* @return bool
13+
*
14+
* @throws \Exception
15+
*/
16+
public static function hasChecksum(string $hex): bool
17+
{
18+
$mix = 0;
19+
20+
foreach (str_split($hex) as $char) {
21+
if ($char >= '0' && $char <= '9') {
22+
$mix |= 0x00;
23+
}
24+
elseif ($char >= 'a' && $char <= 'f') {
25+
$mix |= 0x01;
26+
}
27+
elseif ($char >= 'A' && $char <= 'F') {
28+
$mix |= 0x02;
29+
}
30+
else {
31+
throw new \Exception('Input is not an hexadecimal string');
32+
}
33+
}
34+
35+
return $mix > 2;
36+
}
37+
38+
/**
39+
* @param array $bytes
40+
* @return string
41+
*
42+
* @throws \Exception
43+
*/
44+
public static function encode(array $bytes): string
45+
{
46+
$nibbles = self::byteToNibbles($bytes);
47+
$hashBits = self::bytesToBitsCycle(HashUtil::blake2bHash($bytes, self::BLAKE2B_HASH_LENGTH));
48+
49+
$bitIndex = 0;
50+
foreach ($nibbles as $nibble) {
51+
$char = self::HEX_CHARS[$nibble];
52+
$result[] = ($char >= 'a' && $char <= 'f') && $hashBits[$bitIndex++]
53+
? strtoupper($char)
54+
: $char;
55+
}
56+
57+
return implode($result ?? []);
58+
}
59+
60+
/**
61+
* @param string $hex
62+
* @return array
63+
*
64+
* @throws \Exception
65+
*/
66+
public static function decode(string $hex): array
67+
{
68+
$bytes = ByteUtil::hexToByteArray($hex);
69+
70+
if (!self::hasChecksum($hex)) {
71+
return $bytes;
72+
}
73+
74+
$encoded = self::encode($bytes);
75+
76+
if ($encoded !== $hex) {
77+
throw new \Exception('Invalid checksum');
78+
}
79+
80+
return $bytes;
81+
}
82+
83+
/**
84+
* @param array $bytes
85+
* @return int[]
86+
*
87+
* @throws \Exception
88+
*/
89+
private static function byteToNibbles(array $bytes): array
90+
{
91+
foreach ($bytes as $byte) {
92+
$nibbles[] = $byte >> 4;
93+
$nibbles[] = $byte & 0x0F;
94+
}
95+
96+
return $nibbles;
97+
}
98+
99+
/**
100+
* @param array $bytes
101+
* @return int[]
102+
*/
103+
private static function bytesToBitsCycle(array $bytes): array
104+
{
105+
for ($i = 0; $i < count($bytes); $i++) {
106+
for ($j = 0; $j < 8; $j++) {
107+
$bits[] = ($bytes[$i] >> $j) & 0x01;
108+
}
109+
}
110+
111+
return $bits;
112+
}
113+
}

src/Util/HashUtil.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,22 @@
22

33
namespace Casper\Util;
44

5-
use deemru\Blake2b;
6-
75
class HashUtil
86
{
9-
private static Blake2b $blake2b;
10-
117
/**
128
* @param int[]
9+
* @param int $length
1310
* @return int[]
1411
*
15-
* @throws \Exception
12+
* @throws \SodiumException
1613
*/
17-
public static function blake2bHash(array $bytes): array
14+
public static function blake2bHash(array $bytes, int $length = 32): array
1815
{
19-
if (!isset(self::$blake2b)) {
20-
self::$blake2b = new Blake2b();
21-
}
16+
$hashState = sodium_crypto_generichash_init('', $length);
17+
sodium_crypto_generichash_update($hashState, ByteUtil::byteArrayToString($bytes));
2218

2319
return ByteUtil::stringToByteArray(
24-
self::$blake2b->hash(
25-
ByteUtil::byteArrayToString($bytes)
26-
)
20+
sodium_crypto_generichash_final($hashState, $length)
2721
);
2822
}
2923
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Tests\Functional\Util;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
use Casper\Util\ByteUtil;
8+
use Casper\Util\CEP57ChecksumUtil;
9+
10+
class CEP57ChecksumUtilTest extends TestCase
11+
{
12+
/**
13+
* @throws \Exception
14+
*/
15+
public function testHasCheckSum(): void
16+
{
17+
$hexWithCheckSum = 'de8649985929090b7CB225e35a5A7B4087fb8fcB3D18C8C9a58DA68e4edA8a2E';
18+
$this->assertTrue(CEP57ChecksumUtil::hasChecksum($hexWithCheckSum));
19+
20+
$hexWithoutCheckSum = 'de8649985929090b7cb225e35a5a7b4087fb8fcb3d18c8c9a58da68e4eda8a2e';
21+
$this->assertFalse(CEP57ChecksumUtil::hasChecksum($hexWithoutCheckSum));
22+
}
23+
24+
/**
25+
* @throws \Exception
26+
*/
27+
public function testEncode(): void
28+
{
29+
$hex = 'de8649985929090b7CB225e35a5A7B4087fb8fcB3D18C8C9a58DA68e4edA8a2E';
30+
$bytes = ByteUtil::hexToByteArray($hex);
31+
32+
$encodedHex = CEP57ChecksumUtil::encode($bytes);
33+
$this->assertEquals($hex, $encodedHex);
34+
}
35+
36+
/**
37+
* @throws \Exception
38+
*/
39+
public function testDecode(): void
40+
{
41+
$hex = 'de8649985929090b7CB225e35a5A7B4087fb8fcB3D18C8C9a58DA68e4edA8a2E';
42+
43+
$decodedByteArray = CEP57ChecksumUtil::decode($hex);
44+
$this->assertIsArray($decodedByteArray);
45+
$this->assertCount(32, $decodedByteArray);
46+
}
47+
}

0 commit comments

Comments
 (0)