Zihao

Make small but daily progress

0%

比特币地址生成与验证Nodejs版

本篇文章主要讲了比特币地址是如何生成的,然后使用nodejs 进行了验证。

一、 地址生成流程

  1. 获取一个随机的32字节ECDSA密钥
    9a9a6539856be209b8ea2adbd155c0919646d108515b60b7b13d6a79f1ae5174

  2. 使用椭圆曲线加密算法(ECDSA-secp256k1)计算上述私钥所对应的非压缩公钥:
    0340A609475AFA1F9A784CAD0DB5D5BA7DBAAB2147A5D7B9BBDE4D1334A0E40A5E

  3. 对(压缩)公钥进行SHA-256哈希计算:
    2b0995c0703c96d694f03a8987f89d387459fc359694737547a75764989c5e16

  4. 对步骤2的哈希值进行RIPEMD-160哈希计算
    154de7cabbb5822075e92c57a27ca3ef3e8be50c

  5. 在步骤3的哈希值前添加地址版本号(主网为0x00,测试网为0xef)
    00154de7cabbb5822075e92c57a27ca3ef3e8be50c

  6. 对步骤4的扩展RIPEMD-160哈希值进行SHA256哈希计算
    ab7d579d497d75ab7e337212345635a4c071c249c6e8ec07532d2ea4d82290e6

  7. 对步骤5的哈希值再次进行SHA256哈希计算
    fc897c2001ef5e99b2e37853e84dd041bebe6f831f462729de2af27e4ab9ea7e

  8. 取步骤6结果值的前4个字节作为校验码 fc897c20

  9. 将校验码添加到步骤4的扩展RIPEMD-160哈希值末尾:
    00154de7cabbb5822075e92c57a27ca3ef3e8be50cfc897c20

  10. 使用Base58Check编码将结果从字节字符串转换为base58字符串。12weWzbq5jT7c3MHbHD2WP2uLXEUtaGLXZ

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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. 检验版本号的合法性(根据主网参数校验)

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

欢迎关注我的其它发布渠道