さまざまな言語のビットコインアドレスフォーム検証スクリプトをいくつか見ました ですが、驚くべきことに、JavascriptとPHPの2つの一般的なWeb言語では何も見つかりません。
これはPython用のものですが、PHPおよび/またはJS用のものはありますか?
from hashlib import sha256
digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def decode_base58(bc, length):
n = 0
for char in bc:
n = n * 58 + digits58.index(char)
return n.to_bytes(length, 'big')
def check_bc(bc):
bcbytes = decode_base58(bc, 25)
return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]
if __name__ == '__main__':
bc = '1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'
assert check_bc(bc)
assert not check_bc( bc.replace('N', 'P', 1) )
assert check_bc('1111111111111111111114oLvT2')
assert check_bc("17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j")
これがJSFiddleです:http://jsfiddle.net/timrpeterson/XsCQq/2/
そしてこれがJSFiddleのベースとなっている完全なコードです :
<html>
<head>
<script type="text/javascript" src="http://dl.dropboxusercontent.com/u/28441300/BigInt.js"></script>
<script type="text/javascript" src="http://dl.dropboxusercontent.com/u/28441300/sha256.js"></script>
</head>
<body>
<div id="text">
</div>
<script type="text/javascript">
var address = "1Eym7pyJcaambv8FG4ZoU8A4xsiL9us2zz";
if (check(address)) {
document.getElementById('text').innerHTML += "valid";
} else {
document.getElementById('text').innerHTML += "invalid";
}
function check(address) {
var decoded = base58_decode(address);
if (decoded.length != 25) return false;
var cksum = decoded.substr(decoded.length - 4);
var rest = decoded.substr(0, decoded.length - 4);
var good_cksum = hex2a(sha256_digest(hex2a(sha256_digest(rest)))).substr(0, 4);
if (cksum != good_cksum) return false;
return true;
}
function base58_decode(string) {
var table = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
var table_rev = new Array();
var i;
for (i = 0; i < 58; i++) {
table_rev[table[i]] = int2bigInt(i, 8, 0);
}
var l = string.length;
var long_value = int2bigInt(0, 1, 0);
var num_58 = int2bigInt(58, 8, 0);
var c;
for(i = 0; i < l; i++) {
c = string[l - i - 1];
long_value = add(long_value, mult(table_rev[c], pow(num_58, i)));
}
var hex = bigInt2str(long_value, 16);
var str = hex2a(hex);
var nPad;
for (nPad = 0; string[nPad] == table[0]; nPad++);
var output = str;
if (nPad > 0) output = repeat("\0", nPad) + str;
return output;
}
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
function a2hex(str) {
var aHex = "0123456789abcdef";
var l = str.length;
var nBuf;
var strBuf;
var strOut = "";
for (var i = 0; i < l; i++) {
nBuf = str.charCodeAt(i);
strBuf = aHex[Math.floor(nBuf/16)];
strBuf += aHex[nBuf % 16];
strOut += strBuf;
}
return strOut;
}
function pow(big, exp) {
if (exp == 0) return int2bigInt(1, 1, 0);
var i;
var newbig = big;
for (i = 1; i < exp; i++) {
newbig = mult(newbig, big);
}
return newbig;
}
function repeat(s, n){
var a = [];
while(a.length < n){
a.Push(s);
}
return a.join('');
}
</script>
</body>
</html>
そしてここにPHP例(あなたが持っていると仮定してPHP BC-Math):
<?php
function checkAddress($address)
{
$origbase58 = $address;
$dec = "0";
for ($i = 0; $i < strlen($address); $i++)
{
$dec = bcadd(bcmul($dec,"58",0),strpos("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",substr($address,$i,1)),0);
}
$address = "";
while (bccomp($dec,0) == 1)
{
$dv = bcdiv($dec,"16",0);
$rem = (integer)bcmod($dec,"16");
$dec = $dv;
$address = $address.substr("0123456789ABCDEF",$rem,1);
}
$address = strrev($address);
for ($i = 0; $i < strlen($origbase58) && substr($origbase58,$i,1) == "1"; $i++)
{
$address = "00".$address;
}
if (strlen($address)%2 != 0)
{
$address = "0".$address;
}
if (strlen($address) != 50)
{
return false;
}
if (hexdec(substr($address,0,2)) > 0)
{
return false;
}
return substr(strtoupper(hash("sha256",hash("sha256",pack("H*",substr($address,0,strlen($address)-8)),true))),0,8) == substr($address,strlen($address)-8);
}
?>
これが@ Tim-Petersonの答えのより良いバージョンです。彼が使用していたbase58の実装を修正します(アドレス「12EJmB3cMGRNveskzA7g7kxW32gSbo2dHF」を検証しません。
検証コードを必要なすべてのライブラリと組み合わせて、不要なものをたくさん削除しました。単一のAPI「checkAddress」のみを公開します。モジュールのソースまたは縮小版をダウンロードできる小さなホームページを作成しました: http://www.julianhaight.com/javascript.shtml
修正されたbase58_decode( https://github.com/cryptocoinjs/bs58 から):
// from https://github.com/cryptocoinjs/bs58
// Base58 encoding/decoding
// Originally written by Mike Hearn for BitcoinJ
// Copyright (c) 2011 Google Inc
// Ported to JavaScript by Stefan Thomas
// Merged Buffer refactorings from base58-native by Stephen Pair
// Copyright (c) 2013 BitPay Inc
var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
var ALPHABET_MAP = {}
for(var i = 0; i < ALPHABET.length; i++) {
ALPHABET_MAP[ALPHABET.charAt(i)] = i
}
var BASE = 58
function base58_decode(string) {
if (string.length === 0) return []
var i, j, bytes = [0]
for (i = 0; i < string.length; i++) {
var c = string[i]
if (!(c in ALPHABET_MAP)) throw new Error('Non-base58 character')
for (j = 0; j < bytes.length; j++) bytes[j] *= BASE
bytes[0] += ALPHABET_MAP[c]
var carry = 0
for (j = 0; j < bytes.length; ++j) {
bytes[j] += carry
carry = bytes[j] >> 8
bytes[j] &= 0xff
}
while (carry) {
bytes.Push(carry & 0xff)
carry >>= 8
}
}
// deal with leading zeros
for (i = 0; string[i] === '1' && i < string.length - 1; i++) bytes.Push(0)
bytes = bytes.reverse()
output = '';
for (i=0; i<bytes.length; i++) {
output += String.fromCharCode(bytes[i]);
}
return output;
}
上記の回答に基づいてこれを行うための簡単なPHPライブラリを作成しました。これは 関連するgithubリポジトリで :
<?php
class Btc_address_validator {
/**
* [validate description]
* @param String $address BTC Address string
* @return Boolean validation result
*/
public function validate($address)
{
$addr = $this->decode_base58($address);
if (strlen($addr) != 50)
{
return false;
}
$check = substr($addr, 0, strlen($addr) - 8);
$check = pack("H*", $check);
$check = strtoupper(hash("sha256", hash("sha256", $check, true)));
$check = substr($check, 0, 8);
return $check == substr($addr, strlen($addr) - 8);
}
private function encode_hex($dec)
{
$hexchars = "0123456789ABCDEF";
$return = "";
while (bccomp($dec, 0) == 1)
{
$dv = (string) bcdiv($dec, "16", 0);
$rem = (integer) bcmod($dec, "16");
$dec = $dv;
$return = $return . $hexchars[$rem];
}
return strrev($return);
}
/**
* Convert a Base58-encoded integer into the equivalent hex string representation
*
* @param string $base58
* @return string
* @access private
*/
private function decode_base58($base58)
{
$origbase58 = $base58;
$base58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
$return = "0";
for ($i = 0; $i < strlen($base58); $i++)
{
$current = (string) strpos($base58chars, $base58[$i]);
$return = (string) bcmul($return, "58", 0);
$return = (string) bcadd($return, $current, 0);
}
$return = $this->encode_hex($return);
//leading zeros
for ($i = 0; $i < strlen($origbase58) && $origbase58[$i] == "1"; $i++)
{
$return = "00" . $return;
}
if (strlen($return) % 2 != 0)
{
$return = "0" . $return;
}
return $return;
}
}
ビットコインアドレス(例:3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC)は、多くのPHPの例では無効です。特に上記のアドレスに対して正常に機能する例の1つは、次のとおりです。
Javascriptを使用している場合は、 wallet-address-validator javascriptプラグインを使用できます。
<script src="wallet-address-validator.min.js"></script>
// WAValidator is stored in the windows object
networkType-オプション。 「prod」(デフォルト)を使用して標準アドレスを適用し、「testnet」を使用してテストネットアドレスを適用し、「both」を使用して何も適用しません。
var valid = WAValidator.validate('12h7E1q5UUoPgZ1VtcYb57maFF9Cbk4u5X','BTC','both');
if(valid){
alert('This is a valid address');
} else {
alert('Address INVALID');
}
// will alert "This is a valid address"
var valid = WAValidator.validate('12h7E1q5UUoPgZ1VtcYb57maFF9Cbk4u5X', 'ETH', 'both');
if(valid){
alert('This is a valid address');
} else {
alert('Address INVALID');
}
// will alert "Address INVALID"
これは、 CryptoJS に依存するJavascriptの短くて最新の実装です。
import sha256 from 'crypto-js/sha256'
import CryptoJS from 'crypto-js'
function isBTCAddress (address) {
if (!/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) return false
const bufferLength = 25
let buffer = new Uint8Array(bufferLength)
const digits58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
for (var i = 0; i < address.length; i++) {
const num = digits58.indexOf(address[i])
// buffer = buffer * 58 + num
let carry = 0
for (var j = bufferLength - 1; j >= 0; --j) {
// num < 256, so we just add it to last
const result = buffer[j] * 58 + carry + (j === bufferLength - 1 ? num : 0)
buffer[j] = result % (1 << 8)
carry = Math.floor(result / (1 << 8))
}
}
// check whether sha256(sha256(buffer[:-4]))[:4] === buffer[-4:]
const hashedWords1 = sha256(CryptoJS.lib.WordArray.create(buffer.slice(0, 21)))
const hashedWords = sha256(hashedWords1).words
// get buffer[-4:] with big-endian
const lastWordAddress = new DataView(buffer.slice(-4).buffer).getInt32(0, false)
const expectedLastWord = hashedWords[0]
return lastWordAddress === expectedLastWord
}