一文详解比特币的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 个字符(避免了容易混淆的字符,如 0OIl),确保地址格式更短且适合人类阅读。
P2PKH 地址 (Base58Check 编码)

这个地址是比特币网络的标准 P2PKH 地址格式。

6、 生成的 P2PKH 地址示例:

最终得到的地址是一个以 1 开头的字符串(因为 0x00 版本字节在 Base58 编码中是 1),例如:

1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

这个地址是比特币网络的标准 P2PKH 地址格式。

总结生成流程:

  1. 生成私钥:随机生成一个 256 位的私钥。
  2. 生成公钥:通过椭圆曲线算法 secp256k1 从私钥生成对应的公钥。
  3. 生成 Hash160:对公钥先进行一次 SHA-256,再进行一次 RIPEMD-160 哈希,得到 20 字节的哈希值。
  4. 添加版本字节:将 0x00 作为前缀,代表 P2PKH 地址类型。
  5. 生成校验码:对前面结果进行两次 SHA-256 哈希,取前 4 字节作为校验码,并附加到末尾。
  6. Base58Check 编码:对结果进行 Base58Check 编码,得到最终的比特币 P2PKH 地址。

代码示例:

typescript 版本:

import * as crypto from 'crypto';
import bs58check from 'bs58check';
import * as secp256k1 from 'secp256k1';

// 生成私钥和公钥
function generateKeyPair(): { privateKey: Buffer; publicKey: Buffer } {
  // 生成32字节私钥
  let privateKey: Buffer;
  do {
    privateKey = crypto.randomBytes(32);
  } while (!secp256k1.privateKeyVerify(privateKey));

  // 使用私钥生成公钥,使用压缩格式
  const publicKeyUint8Array = secp256k1.publicKeyCreate(privateKey, true);

  // 将公钥 Uint8Array 转换为 Buffer
  const publicKey = Buffer.from(publicKeyUint8Array);

  return { privateKey, publicKey };
}

// 计算SHA256 -> RIPE-MD160
function hash160(data: Buffer): Buffer {
  // 先对数据进行SHA-256
  const sha256Hash = crypto.createHash('sha256').update(data).digest();

  // 再对结果进行RIPEMD-160
  return crypto.createHash('ripemd160').update(sha256Hash).digest();
}

// 计算双SHA-256并取前4个字节作为校验和
function checksum(payload: Buffer): Buffer {
  // 第一次SHA-256
  const firstHash = crypto.createHash('sha256').update(payload).digest();

  // 第二次SHA-256
  const secondHash = crypto.createHash('sha256').update(firstHash).digest();

  // 取前4个字节作为校验和
  return secondHash.slice(0, 4);
}

// 生成比特币P2PKH地址
export function generateP2PKHAddress(): string {
  // 1. 生成私钥和公钥
  const { publicKey } = generateKeyPair();

  // 2. 公钥哈希: 先SHA-256,再RIPEMD-160
  const publicKeyHash = hash160(publicKey);

  // 3. 添加网络前缀,0x00表示比特币主网地址
  const versionedPayload = Buffer.concat([Buffer.from([0x00]), publicKeyHash]);

  // 4. 计算校验和,取前4个字节
  const check = checksum(versionedPayload);

  // 5. 拼接校验和,生成完整的payload
  const fullPayload = Buffer.concat([versionedPayload, check]);

  // 6. Base58Check编码
  const address = bs58check.encode(fullPayload);

  return address;
}

go 版本

package utils

import (
	"crypto/sha256"
	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcutil/base58"
	"golang.org/x/crypto/ripemd160"
	"log"
)

// 生成私钥和公钥
func generateKeyPair() (*btcec.PrivateKey, []byte) {
	//
	privateKey, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		log.Fatal(err)
	}

	// 公钥采用压缩格式
	publicKey := privateKey.PubKey().SerializeCompressed()

	return privateKey, publicKey
}

// 计算SHA256 -> RIPE-MD160
func hash160(data []byte) []byte {
	// 先对数据进行SHA-256
	sha256Hash := sha256.Sum256(data)

	// 再对结果进行RIPE-MD-160
	ripemd160Hasher := ripemd160.New()
	_, err := ripemd160Hasher.Write(sha256Hash[:])
	if err != nil {
		log.Fatal(err)
	}

	return ripemd160Hasher.Sum(nil)
}

// 计算双SHA-256并取前4个字节作为校验和
func checksum(payload []byte) []byte {
	// 第一次SHA-256
	firstHash := sha256.Sum256(payload)

	// 第二次SHA-256
	secondHash := sha256.Sum256(firstHash[:])

	// 取前4个字节作为校验和
	return secondHash[:4]
}

func GenerateP2PKHAddress() string {
	// 1. 生成私钥和公钥
	_, publicKey := generateKeyPair()

	// 2. 公钥哈希: 先SHA-256,再RIPEMD-160
	publicKeyHash := hash160(publicKey)

	// 3. 添加网络前缀,0x00表示比特币主网地址
	versionedPayload := append([]byte{0x00}, publicKeyHash...)

	// 4. 计算校验和,取前4个字节
	checksum := checksum(versionedPayload)

	// 5. 拼接校验和,生成完整的payload
	fullPayload := append(versionedPayload, checksum...)

	// 6. Base58Check编码
	address := base58.Encode(fullPayload)

	return address
}

原文链接:https://juejin.cn/post/7426035420728672266 作者:我是区块链小学生

(0)
上一篇 2024年9月21日 上午10:00
下一篇 2024年10月30日 下午4:00

相关推荐

发表回复

登录后才能评论