diff --git a/src/AddressValidator.php b/src/AddressValidator.php index 6c33932..75930ef 100644 --- a/src/AddressValidator.php +++ b/src/AddressValidator.php @@ -13,14 +13,53 @@ class AddressValidator { CONST TESTNET_PUBKEY = "6F"; CONST TESTNET_SCRIPT = "C4"; + CONST GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + CONST CHARKEY_KEY = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 + ]; static public function typeOf($addr) { - - if (preg_match('/[^1-9A-HJ-NP-Za-km-z]/', $addr)) { + + + + // bech32 https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + if(preg_match('/^(bc|tb)1([023456789acdefghjklmnpqrstuvwxyz]+[023456789acdefghjklmnpqrstuvwxyz]{6})$/', $addr, $match)) { + + $hrp = $match[1]; + $chars = array_values(unpack('C*', $match[2])); + + $data = []; + foreach ($chars as $char) { + $data[] = ($char & 0x80) ? -1 : self::CHARKEY_KEY[$char]; + } + + $polyMod = self::polyMod(array_merge(self::hrpExpand($hrp),$data)); + + if($hrp=='bc') { + return self::MAINNET_PUBKEY; + } + if($hrp=='tb') { + return self::TESTNET_PUBKEY; + } + } + + + if (preg_match('/[^1-9A-HJ-NP-Za-km-z]/', $addr) ) { + return false; } + + $decoded = self::decodeAddress($addr); - + + if (strlen($decoded) != 50) { return false; } @@ -65,7 +104,7 @@ static public function isValid($addr, $version = null) { $valids = [$version]; break; default: - throw new Exception('Unknown version constant'); + throw new \Exception('Unknown version constant'); } return in_array($type, $valids); @@ -103,4 +142,31 @@ static protected function decodeAddress($data) { return $withPadding; } + static protected function polyMod(array $values) + { + $numValues = count($values); + $chk = 1; + for ($i = 0; $i < $numValues; $i++) { + $top = $chk >> 25; + $chk = ($chk & 0x1ffffff) << 5 ^ $values[$i]; + for ($j = 0; $j < 5; $j++) { + $value = (($top >> $j) & 1) ? self::GENERATOR[$j] : 0; + $chk ^= $value; + } + } + return $chk; + } + static protected function hrpExpand($hrp) + { + $hrpLen = strlen($hrp); + $expand1 = []; + $expand2 = []; + for ($i = 0; $i < $hrpLen; $i++) { + $o = \ord($hrp[$i]); + $expand1[] = $o >> 5; + $expand2[] = $o & 31; + } + return \array_merge($expand1, [0], $expand2); + } + } diff --git a/tests/AddressValidatorTest.php b/tests/AddressValidatorTest.php index ae14944..8d42aa1 100644 --- a/tests/AddressValidatorTest.php +++ b/tests/AddressValidatorTest.php @@ -73,7 +73,9 @@ class AddressValidatorTest extends PHPUnit_Framework_TestCase { '1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu', '1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4', '19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r', - '13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE' + '13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE', + 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', + 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3', ]; protected $validTestnet = [ @@ -82,7 +84,9 @@ class AddressValidatorTest extends PHPUnit_Framework_TestCase { 'n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk', 'mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H', 'mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6', - 'myoqcgYiehufrsnnkqdqbp69dddVDMopJu' + 'myoqcgYiehufrsnnkqdqbp69dddVDMopJu', + 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx', + 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7' ]; protected $scriptAddrs = [