使用 Node.js 校验比特币地址是否合法

如何判断比特币地址是否合法

判断一个给定的比特币地址是否合法,其实就相当于把流程倒推一下进行校验就好。

  1. 把地址 base58 解码成数组
  2. 把数组分成两个数组,数组一是地址校验和,数组二是地址的公钥哈希
  3. 地址的公钥哈希两次 Sha256 Hash
  4. 两次 Sha256 后的前 4 位,跟数组一比较。判断校验和是否相等(判断 1)
  5. 校验通过的解码,获取版本号
  6. 判断版本号(判断 2)

校验比特币地址的代码

isBitcoinAddress(address) {
    try {
        // 第10步:base58解码
        const arr = bs58.decode(address);
        const buf = Buffer.from(arr);
        console.log(arr);

        // 第9步:分成两个字节数组
        const checksum = buf.slice(-4);
        const bytes = buf.slice(0, buf.length - 4);
        console.log(checksum.toString(`hex`));
        console.log(bytes.toString(`hex`));

        // 第6步(SHA256)
        const shax1 = crypto.createHash(`sha256`).update(bytes).digest();
        console.log(shax1.toString(`hex`));

        // 第7步(SHA256)
        const shax2 = crypto.createHash(`sha256`).update(shax1).digest();
        console.log(shax2.toString(`hex`));

        // 第8步.取校验和
        const newChecksum = shax2.slice(0, 4);

        // 校验和跟字节数组[checksum]比较。如果相同校验通过。
        if (checksum.toString('hex') !== newChecksum.toString('hex')) {
            throw new Error('Invalid checksum');
        }

        // 第5步.比特币主网版本号
        const version = buf.toString('hex').slice(0, 2);
        console.log(version);

        // 检验版本号的合法性(主网00)00 为普通地址,05为脚本地址,注意大小写。
        if (version !== '00' && version !== '05') {
            throw new Error('Invalid version');
        }
    } catch (e) {
        return false;
    }
    return true;
}

最终的比特币地址的创建和校验测试

"use strict";
const crypto = require('crypto');
const bs58 = require(`bs58`);

let utility = {
    // 生成比特币地址
    createBitcoinAddress() {
        // 第1步
        let privateKey = crypto.randomBytes(32);
        // Test
        // let privateKey = Buffer.from('18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725', 'hex');

        // 第2步
        let ecdh = crypto.createECDH('secp256k1').setPrivateKey(privateKey);
        let cpublicKey = Buffer.from(ecdh.getPublicKey('hex', 'compressed'), 'hex');

        // 第3步
        let sha1 = crypto.createHash('sha256').update(Buffer.from(cpublicKey, 'hex')).digest();

        // 第4步
        let pubkeyHash = crypto.createHash('ripemd160').update(sha1).digest();

        // 第5步: 添加 Version
        const version = Buffer.from([0x00]);
        let extendedPriKey = Buffer.alloc(version.length + pubkeyHash.length);
        extendedPriKey = Buffer.concat([version, pubkeyHash], extendedPriKey.length);

        // 第6步: 将 ECDSA_PublicKey_SHA256_RIPEMD160 进行第1次 SHA256 计算
        let sha2 = crypto.createHash(`sha256`).update(Buffer.from(extendedPriKey, 'hex')).digest();


        // 第7步: 将第6步的结果进行第2次 SHA256 计算
        let sha3 = crypto.createHash('sha256').update(sha2).digest();

        // 第8步: 取第二次 SHA256Hash 结果的前 4 个字节(这是地址的校验和)
        let checksum = Buffer.alloc(4);
        sha3.copy(checksum, 0, 0, checksum.length);

        // 第9步: 计算
        let btcAddress = Buffer.alloc(extendedPriKey.length + checksum.length);
        btcAddress = Buffer.concat([extendedPriKey, checksum], btcAddress.length);

        // 第10步: base58
        let address = bs58.encode(btcAddress);

        return {
            address,
            privateKey: privateKey.toString(`hex`)
        };
    },

    // 校验地址是否合法
    isBitcoinAddress(address) {
        try {
            // 第10步:base58解码
            const arr = bs58.decode(address);
            const buf = Buffer.from(arr);
            console.log(arr);

            // 第9步:分成两个字节数组
            const checksum = buf.slice(-4);
            const bytes = buf.slice(0, buf.length - 4);
            console.log(checksum.toString(`hex`));
            console.log(bytes.toString(`hex`));

            // 第6步(SHA256)
            const shax1 = crypto.createHash(`sha256`).update(bytes).digest();
            console.log(shax1.toString(`hex`));

            // 第7步(SHA256)
            const shax2 = crypto.createHash(`sha256`).update(shax1).digest();
            console.log(shax2.toString(`hex`));

            // 第8步.取校验和
            const newChecksum = shax2.slice(0, 4);

            // 校验和跟字节数组[checksum]比较。如果相同校验通过。
            if (checksum.toString('hex') !== newChecksum.toString('hex')) {
                throw new Error('Invalid checksum');
            }

            // 第5步.比特币主网版本号
            const version = buf.toString('hex').slice(0, 2);
            console.log(version);

            // 检验版本号的合法性(主网00)00 为普通地址,05为脚本地址,注意大小写。
            if (version !== '00' && version !== '05') {
                throw new Error('Invalid version');
            }
        } catch (e) {
            return false;
        }
        return true;
    }
}
// 创建
let newAddress = utility.createBitcoinAddress();
console.log('创建:比特币地址', newAddress);

// 校验
console.log('校验:比特币地址', utility.isBitcoinAddress(newAddress.address));

运行结果如下

创建:比特币地址 {
  address: '1Bskdmqj8pJfugmZ7xw6Kc5YFjfsyCnneS',
  privateKey: '49550d988169a6c55bb49f3c030b4a9965aac2cf274dbaa8e5950c666b129c3a'
}
<Buffer 00 77 4a 7a d2 57 70 03 4e 63 84 c8 35 27 18 5e d0 a6 af ad be bb dc 85 07>
bbdc8507
00774a7ad25770034e6384c83527185ed0a6afadbe
e7ed049416f21aa61b8587284c7337e9ad4a0533ed24d0e2b89019347bb8133f
bbdc8507021152b4e2e67d85eb9953efd97cf629a5a76f98d1234207157146f5
00
校验:比特币地址 true

通过测试,完美运行。