LoginSignup
2
1

More than 1 year has passed since last update.

bitcoin地址是如何生成的

Last updated at Posted at 2019-02-12

手动生成 Bitcoin 地址看似有点儿事倍功半,如果你了解了这个过程,就会明白眼花缭乱的 Bitcoin 分叉币地址只是私钥的不同显示方式,对了解 Eth EOS 地址也很有帮助,也可以更清楚的了解 Bitcoin 是如何花费的,一通则百通,妙哉。

我们使用私钥 0xccea9c5a20e2b78c2e0fbdd8ae2d2b67e6b1894ccb7a55fc1de08bd53994ea64 生成了一个主网地址14xfJr1DArtYR156XBs28FoYk6sQqirT2s

生成 Bitcoin 地址,先由私钥生成公钥,再有公钥生成 hash160,最后 hash160 再进行 base58 运算得到地址,如下图所示:

由私钥得到公钥,是由 ECDSA 实现的。ECDSA 是Elliptic Curve Digital Signature Algorithm的缩写,即椭圆曲线数字签名算法

椭圆曲线其实不是通常认为的椭圆,而是类似下面的模样:

比特币用到的椭圆曲线是 secp256k1: $y^2=x^3+7$

生成比特币地址

言归正传,正式进入主题,我将这个过程分成了 8 个步骤。

Step1. 生成私钥

Bitcoin 要使用到 Secp256k1 这条特殊的椭圆曲线得到公私钥,
我们通过 OpenSSL 命令来生成私钥。

openssl ecparam -name secp256k1 -genkey > priv.pem

# 输出DER格式
openssl ec -in priv.pem -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32

# 结果
ccea9c5a20e2b78c2e0fbdd8ae2d2b67e6b1894ccb7a55fc1de08bd53994ea64

这里的结果是个 16 进制数据
0xccea9c5a20e2b78c2e0fbdd8ae2d2b67e6b1894ccb7a55fc1de08bd53994ea64

更快捷的方式是 openssl rand 32 -hex

Step2. 生成公钥

通过 priv.pem 生成 pub_key

openssl ec -in priv.pem -pubout -outform DER | tail -c 65 | xxd -p -c 65

# 输出未压缩公钥
04d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f691757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d

得到压缩公钥

openssl ec -in priv.pem -pubout -conv_form compressed -outform DER | tail -c 33 | xxd -p -c 33

# 输出
03d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69

输出 DER 格式,字符长度是 130

pub_key = 04d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f691757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d

这是未压缩公钥,压缩公钥是 03d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69

一步到位生成私钥、公钥

openssl ecparam -genkey -name secp256k1 -text -noout -outform DER | \
xxd -p -c 1000 | \
sed 's/41534e31204f49443a20736563703235366b310a30740201010420/PrivKey: /' | \
sed 's/a00706052b8104000aa144034200/\'$'\nPubKey: /'

详细内容请看压缩公钥与非压缩公钥

Step3. 第 2 步结果进行 hash160 运算

hash160 运算 就是先进行 SHA256,再进行 RMD160。

bytes = [pub_key].pack("H*") # 转为 16 进制
hash160_val = Digest::RMD160.hexdigest(Digest::SHA256.digest(bytes) )

hash160_val = 2b6f3b9e337cedbb7c40839523fb1100709c12f7

Step4. 第 3 步结果加上前缀符

普通的主网地址的前缀符是00,Bitcoin 地址前缀符有很多种,具体看 https://en.bitcoin.it/wiki/List_of_address_prefixes

'00'+ '2b6f3b9e337cedbb7c40839523fb1100709c12f7'

step_04 = 002b6f3b9e337cedbb7c40839523fb1100709c12f7

Step5. 第 4 步结果,执行 2 次 SHA256, 取前 8 位作为校验和

hex_str = [step_04].pack("H*")
checksum = Digest::SHA256.hexdigest(Digest::SHA256.digest(hex_str) )[0...8]

得到 checksum = 86b2e90c

Step6. 第 4 步结果跟第 5 步结果合并

'002b6f3b9e337cedbb7c40839523fb1100709c12f7' + '86b2e90c'
# step_04 + checksum

step_06 = 002b6f3b9e337cedbb7c40839523fb1100709c12f786b2e90c

Step7. Base58 编码

Base58 是一种独特的编码方式,是 Base64 的变形,主要用于 Bitcoin 的钱包地址。相比 Base64,Base58 去掉了数字0,大写字母O,大写字母I,小写字母l+/,避免引起视觉混淆。

来个 base58 算法

def encode_base58(int_val, leading_zero_bytes=0)
  alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
  base58_val, base = '', alpha.size

  while int_val > 0
    int_val, remainder = int_val.divmod(base)
    base58_val = alpha[remainder] + base58_val
  end

  base58_val
end

Step8. 第 6 步结果进行 base58 编码

step_06 = "002b6f3b9e337cedbb7c40839523fb1100709c12f786b2e90c"
leading_zero_bytes = (step_06.match(/^([0]+)/) ? $1 : '').size / 2
# leading_zero_bytes 的作用是字母填充
address = ("1" * leading_zero_bytes) + encode_base58(step_06.to_i(16) )

得到 14xfJr1DArtYR156XBs28FoYk6sQqirT2s,这是一个标准的 bitcoin 地址,终于大功告成。

思考:通过 btc 地址能反向得到 hash160_val 么?

汇总代码: gen_addr.rb

可以使用 bitcoin-ruby 生成地址

require 'bitcoin'

pri_key, pub_key = Bitcoin.generate_key # 私钥 公钥
# 通过 ffi 调用 OpenSSL 得到,很多类库都是这么做的
address = Bitcoin::pubkey_to_address(pub_key)

生成测试网地址

# ruby code
require 'bitcoin'

Bitcoin::network = :testnet #使用测试网
pri_key, pub_key = Bitcoin.generate_key
address = Bitcoin::pubkey_to_address(pub_key)

在 Bitcoin 系统中,私钥能得公钥,公钥能得到钱包地址,
私钥=>公钥=>钱包地址,而反向是不可以的。
牢记你的私钥,而且私钥不能修改地址也不能修改,谁掌握了私钥谁就拥有了这些币!!!

python3 code

## https://github.com/fortesp/bitcoinaddress

from bitcoinaddress import Wallet

wallet = Wallet()
priv_key = wallet.key.hex

print("priv_key: ", priv_key)
print(wallet)

testwallet = Wallet(hash_or_seed=priv_key, testnet=True)
print(testwallet)

参考:

https://bitcoin.stackexchange.com/a/111651
How do these OpenSSL commands create a Bitcoin private/key from a ECDSA keypair
How to convert an ECDSA key to PEM format

https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
https://en.bitcoin.it/wiki/Address
https://en.bitcoin.it/wiki/List_of_address_prefixes
https://github.com/liushooter/learn-blockchain/blob/master/gen_addr.rb

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1