比特币地址生成与验证 Nodejs 版
2018-12-27 本文已影响6人
金牛茶馆
地址生成
地址生成流程:
0 - 获取一个随机的32字节ECDSA密钥
9a9a6539856be209b8ea2adbd155c0919646d108515b60b7b13d6a79f1ae5174
1 - 使用椭圆曲线加密算法(ECDSA-secp256k1)计算上述私钥所对应的非压缩公钥
0340A609475AFA1F9A784CAD0DB5D5BA7DBAAB2147A5D7B9BBDE4D1334A0E40A5E
2 - 对(压缩)公钥进行SHA-256哈希计算
2b0995c0703c96d694f03a8987f89d387459fc359694737547a75764989c5e16
3 - 对步骤2的哈希值进行RIPEMD-160哈希计算
154de7cabbb5822075e92c57a27ca3ef3e8be50c
4 - 在步骤3的哈希值前添加地址版本号(主网为0x00,测试网为0xef)
00154de7cabbb5822075e92c57a27ca3ef3e8be50c
5 - 对步骤4的扩展RIPEMD-160哈希值进行SHA256哈希计算
ab7d579d497d75ab7e337212345635a4c071c249c6e8ec07532d2ea4d82290e6
6 - 对步骤5的哈希值再次进行SHA256哈希计算
fc897c2001ef5e99b2e37853e84dd041bebe6f831f462729de2af27e4ab9ea7e
7 - 取步骤6结果值的前4个字节作为校验码
fc897c20
8 - 将校验码添加到步骤4的扩展RIPEMD-160哈希值末尾
00154de7cabbb5822075e92c57a27ca3ef3e8be50cfc897c20
9 - 使用Base58Check编码将结果从字节字符串转换为base58字符串。
12weWzbq5jT7c3MHbHD2WP2uLXEUtaGLXZ
代码:
async createBitcoinAddress(){
/** Create Bitcoin Address */
const crypto = require(`crypto`);
const ecdh = crypto.createECDH('secp256k1');
const bs58 = require(`bs58`);
// 0 - Having a private ECDSA key
var privateKey = crypto.randomBytes(32);
console.log(`Private key:[${privateKey.toString(`hex`)}]`);
// 1 - Take the corresponding public key generated with it (33 bytes, 1 byte 0x02 (y-coord is even),
// and 32 bytes corresponding to X coordinate)
ecdh.setPrivateKey(privateKey);
var cpublicKey = Buffer.from(ecdh.getPublicKey('hex', 'compressed'), 'hex');
console.log(`Public key:[${cpublicKey.toString(`hex`).toUpperCase()}]`);
// 2 - Perform SHA-256 hashing on the public key
var sha1 = crypto.createHash(`sha256`).update(cpublicKey).digest();
console.log(`SHA-256:[${sha1.toString(`hex`)}]`);
// 3 - Perform RIPEMD-160 hashing on the result of SHA-256
var ripemd160 = crypto.createHash(`rmd160`).update(sha1).digest();
console.log(`RIPEMD-160:[${ripemd160.toString(`hex`)}]`);
// 4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network, 0x6f for Testnet)
const version = Buffer.from([0x00]);
var extendedPriKey = Buffer.alloc(ripemd160.length + version.length);
extendedPriKey = Buffer.concat([version, ripemd160], extendedPriKey.length);
console.log(`Extended RIPEMD-160:[${extendedPriKey.toString(`hex`)}]`);
// 5 - Perform SHA-256 hash on the extended RIPEMD-160 result
var sha2 = crypto.createHash(`sha256`).update(extendedPriKey).digest();
console.log(`SHA-256:[${sha2.toString(`hex`)}]`);
// 6 - Perform SHA-256 hash on the result of the previous SHA-256 hash
var sha3 = crypto.createHash(`sha256`).update(sha2).digest();
console.log(`SHA-256:[${sha3.toString(`hex`)}]`);
// 7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum
var checksum = Buffer.alloc(4);
sha3.copy(checksum, 0, 0, checksum.length);
console.log(`Checksum:[${checksum.toString(`hex`)}]`);
// 8 - Add the 4 checksum bytes from stage 7 at the end of extended RIPEMD-160 hash from stage 4.
// This is the 25-byte binary Bitcoin Address.
var btcAddress = Buffer.alloc(extendedPriKey.length + checksum.length);
btcAddress = Buffer.concat([extendedPriKey, checksum], btcAddress.length);
console.log(`25-byte binary bitcoin address:[${btcAddress.toString(`hex`)}]`);
// 9 - Convert the result from a byte string into a base58 string using Base58Check encoding.
// This is the most commonly used Bitcoin Address format
var address = bs58.encode(btcAddress);
console.log(`Address:[${address}]`);
this.success(address);
}
校验流程
根据生成流程来实现校验方式。
1.把地址base58解码成字节数组
2.把数组分成两个字节数组,字节数组(一)是后4字节数组,字节数组(二)是减去后4字节的数组
3.把字节数组(二)两次Sha256 Hash
4.取字节数组(二)hash后的前4位,跟字节数组(一)比较。如果相同校验通过。
5.校验通过的解码字节数组取第一个字节(0xff),得到版本号
6.检验版本号的合法性(根据主网参数校验)
代码:
async isAddress(address) {
try{
// 1.把地址base58解码成字节数组
const arr = bs58.decode(address);
const buf = new Buffer(arr);
// 2.把数组分成两个字节数组,字节数组(一)是后4字节数组,字节数组(二)是减去后4字节的数组
const checksum = buf.slice(-4);
const bytes = buf.slice(0, buf.length - 4);
// 3.把字节数组(二)两次Sha256 Hash
const shax1 = createHash('sha256').update(bytes).digest();
const shax2 = createHash('sha256').update(shax1).digest();
// 4.取字节数组(二)hash后的前4位,跟字节数组(一)比较。如果相同校验通过。
const newChecksum = shax2.slice(0, 4);
if (checksum.toString('hex') !== newChecksum.toString('hex')) {
throw new Error('Invalid checksum');
}
// 5.校验通过的解码字节数组取第一个字节(0xff),得到版本号
const version = buf.toString('hex').slice(0,2);
// 6.检验版本号的合法性(根据主网参数校验)00 为普通地址,05为脚本地址,注意大小写。
if(version !== '00' && version !== '05'){
throw new Error('Invalid version');
}
}catch(e){
return false;
}
return true;
}
}
参考文档:https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
附网络判断码:
Crypto Coin | Public Address | Private Wallet Import Format | Script Hash |
---|---|---|---|
BTC | 0x00 | 0x80 | 0x05 |
BTC-TEST | 0x6F | 0xEF | 0xC4 |
DOGE | 0x1E | 0x9E | 0x16 |
DOGE-TEST | 0x71 | 0xF1 | 0xC4 |
LTC | 0x30 | 0xB0 | 0x05 |
LTC-TEST | 0x6F | 0xEF | 0xC4 |
NMC | 0x34 | 0xB4 | 0x05 |
PPC | 0x37 | 0xB7 | 0x75 |
URO | 0x44 | 0xC4 | 0x05 |