一文详解比特币的Legacy(P2PKH)地址类型
基本定义:
P2PKH (Pay-to-Public-Key-Hash) 是比特币网络中最常见的地址类型,常被称为标准比特币地址。生成 P2PKH 地址的过程涉及几个步骤,依赖于公钥和 Hash160
这两个核心概念。
生成地址的基本步骤
1、生成私钥和公钥
生成比特币地址的第一步是创建一个 私钥,并从私钥中导出 公钥,
- 私钥: 私钥是一个随机生成的256位数字(32字节)。它可以用于生成对应的公钥,并且必须严格保密。
- 公钥:使用椭圆曲线加密算法 (ECDSA) 从私钥中生成公钥。这个过程是单向的,意味着无法通过公钥反推出私钥。比特币采用的是椭圆曲线
secp256k1
。
私钥 (256位) → 椭圆曲线算法 (secp256k1) → 公钥 (通常是33字节压缩格式)
2、 计算公钥的 Hash160
在生成了公钥之后,接下来要对公钥进行两次哈希运算,以生成 Hash160:
- SHA-256 哈希:首先对公钥执行一次 SHA-256 哈希运算。
- RIPEMD-160 哈希:然后对得到的 SHA-256 哈希值执行 RIPEMD-160 哈希运算,得到一个长度为 20 字节(160 位)的哈希值。
这个 160 位的哈希值是地址生成的核心部分。
公钥 → SHA-256 → 哈希1 (32字节)
哈希1 → RIPEMD-160 → Hash160 (20字节)
3、添加版本前缀:
为了区分不同的地址类型,在 Hash160 前面加上一个 版本字节:
- 对于 P2PKH 地址,这个版本字节是
0x00
,代表这是一个比特币的主网 P2PKH 地址(在测试网中使用的前缀是0x6F
)。
P2PKH 地址前缀 = 0x00
P2PKH 地址 = 0x00 + Hash160
4、 计算校验码:
为了防止地址在传输过程中出错,需要计算并添加一个 校验码:
- 对
版本字节 + Hash160
进行两次 SHA-256 哈希运算。 - 从这个双重哈希的结果中取前 4 个字节,作为校验码,附加在地址的末尾。
Checksum = SHA-256(SHA-256(0x00 + Hash160)) 的前4字节
P2PKH 地址 = 0x00 + Hash160 + 校验码
5、Base58Check 编码:
将生成的 0x00 + Hash160 + 校验码
进行 Base58Check 编码,得到最终的 比特币地址。
- Base58Check 是一种特殊的编码方式,使用了 58 个字符(避免了容易混淆的字符,如
0
、O
、I
和l
),确保地址格式更短且适合人类阅读。
P2PKH 地址 (Base58Check 编码)
这个地址是比特币网络的标准 P2PKH 地址格式。
6、 生成的 P2PKH 地址示例:
最终得到的地址是一个以 1
开头的字符串(因为 0x00
版本字节在 Base58 编码中是 1
),例如:
1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
这个地址是比特币网络的标准 P2PKH 地址格式。
总结生成流程:
- 生成私钥:随机生成一个 256 位的私钥。
- 生成公钥:通过椭圆曲线算法
secp256k1
从私钥生成对应的公钥。 - 生成 Hash160:对公钥先进行一次 SHA-256,再进行一次 RIPEMD-160 哈希,得到 20 字节的哈希值。
- 添加版本字节:将
0x00
作为前缀,代表 P2PKH 地址类型。 - 生成校验码:对前面结果进行两次 SHA-256 哈希,取前 4 字节作为校验码,并附加到末尾。
- Base58Check 编码:对结果进行 Base58Check 编码,得到最终的比特币 P2PKH 地址。
代码示例:
ts 版本:
import * as crypto from 'crypto';
import bs58 from 'bs58';
// 将 16 进制字符串转换为字节数组
function hexStringToBytes(hexStr: string): Buffer {
return Buffer.from(hexStr, 'hex');
}
// 生成比特币的 P2PKH (Legacy) 地址
export function generateBtcLegacyAddress(publicKeyHex: string): string {
// 1. 将公钥从 16 进制字符串转换为字节数组
const publicKeyBytes = hexStringToBytes(publicKeyHex);
// 2. 进行 SHA-256 哈希
const sha256Hash = crypto
.createHash('sha256')
.update(publicKeyBytes)
.digest();
// 3. 进行 RIPEMD-160 哈希
const ripemd160Hash = crypto
.createHash('ripemd160')
.update(sha256Hash)
.digest();
// 4. 添加比特币 P2PKH 地址的前缀 0x00
const version = Buffer.from([0x00]);
const versionedPayload = Buffer.concat([version, ripemd160Hash]);
// 5. 进行双重 SHA-256 哈希,用于生成校验和
const firstSHA = crypto
.createHash('sha256')
.update(versionedPayload)
.digest();
const secondSHA = crypto.createHash('sha256').update(firstSHA).digest();
const checksum = secondSHA.slice(0, 4); // 取前 4 个字节作为校验和
// 6. 将 version, publicKeyHash 和校验和组合在一起
const fullPayload = Buffer.concat([versionedPayload, checksum]);
// 7. 使用 Base58 编码
const address = bs58.encode(fullPayload);
return address;
}
go 版本
package utils
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcutil/base58"
"golang.org/x/crypto/ripemd160"
)
// HexStringToBytes 将 16 进制的字符串转为字节数组
func HexStringToBytes(hexStr string) ([]byte, error) {
return hex.DecodeString(hexStr)
}
func Generate_btc_legacy_address(publicKeyHex string) (string, error) {
// 1. 将公钥从16进制字符串转换为字节数组
publicKeyBytes, err := HexStringToBytes(publicKeyHex)
if err != nil {
return "", fmt.Errorf("invalid public key: %v", err)
}
// 2. 进行 SHA-256 哈希
sha256Hash := sha256.New()
sha256Hash.Write(publicKeyBytes)
publicKeySHA256 := sha256Hash.Sum(nil)
// 3. 进行 RIPEMD-160 哈希
ripemd160Hasher := ripemd160.New()
ripemd160Hasher.Write(publicKeySHA256)
publicKeyHash := ripemd160Hasher.Sum(nil)
// 4. 添加比特币 P2PKH 地址的前缀 0x00
version := []byte{0x00}
versionedPayload := append(version, publicKeyHash...)
// 5. 进行双重 SHA-256 哈希,用于生成校验和
firstSHA := sha256.Sum256(versionedPayload)
secondSHA := sha256.Sum256(firstSHA[:]) // 将 [32]byte 转换为切片
checksumBytes := secondSHA[:4] // 取前4个字节作为校验和
// 6. 将 version, publicKeyHash 和校验和组合在一起
fullPayload := append(versionedPayload, checksumBytes...)
// 7. 使用 Base58 编码
address := base58.Encode(fullPayload)
return address, nil
}
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。