一文详解比特币的Nested SegWit (P2SH)地址类型
基本概念:
比特币的P2SH地址类型(Pay-to-Script-Hash,支付到脚本哈希)是一种常见的比特币地址类型,用于支持更复杂的交易条件。P2SH地址通过使用脚本哈希来实现不同的条件支付方式,最常见的应用是多签名地址和时间锁等。
工作原理:
- 标准比特币地址(如P2PKH,Pay-to-PubKey-Hash)是直接将支付与公钥哈希绑定,资金的解锁条件是持有对应的私钥。
- P2SH地址则是通过一个脚本(称为赎回脚本,Redeem Script)来定义解锁条件,资金不会直接发送到公钥哈希,而是发送到赎回脚本的哈希值(Script Hash)。当需要花费这些资金时,用户必须提供该赎回脚本以及满足该脚本的解锁条件。
P2SH地址的特征
- 以“3”开头:P2SH地址通常以“3”开头,例如
3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
。 - 多签名应用:P2SH最常见的用途是实现多签名交易(multi-signature),如要求多个签名来解锁资金。
- 地址结构:它使用的是脚本的哈希值,而不是公钥哈希。这样可以隐藏脚本的具体内容,直到交易被花费。
P2SH-P2WPKH地址(兼容SegWit的P2SH)
-
前缀:地址以3开头。
-
用途:这种地址类型主要是为了兼容目的,将隔离见证(SegWit)功能引入到现有的支持P2SH的基础设施中。这允许使用旧版钱包和服务也能发送比特币到隔离见证地址,并处理这些用户的支付。
-
结构:具体来说,这是一种P2SH地址,其脚本哈希是一个指向隔离见证(SegWit)输出的脚本,即P2WPKH。这意味着该地址在交易上表现为P2SH,但其解锁脚本是一个内嵌的P2WPKH。
-
验证过程:
-
外层(P2SH层) :在所有者花费比特币时,需要提供指定的解锁脚本哈希对应的实际脚本,它会解锁一个嵌套的P2WPKH。
- 内层(P2WPKH层) :接下来,解锁过程进入SegWit的验证模式,对应的见证数据用于验证交易。
-
优势:
-
兼容性好:使得SegWit功能可以被不支持完全SegWit的旧版钱包和服务所使用,因为这些钱包和服务仍然可以识别和处理P2SH类型的地址。
- 减小交易费用:虽然比原生SegWit(Bech32)地址费用更高,但相较于传统P2PKH地址,因其部分见证数据被隔离,交易费用有所减少。
普通P2SH地址:广泛用于复杂的交易脚本,如多重签名、条件脚本等,其解锁脚本完全由用户自定义。P2SH-P2WPKH地址:为了向后兼容而设计的隔离见证地址,方便用旧版钱包和服务进行交易。同时利用SegWit的优势(如较低的交易费用和改进的结构),但表现为普通P2SH地址。P2SH-P2WPKH可以被看作是向SegWit过渡的一种桥梁,在新旧基础设施之间提供了必要的兼容性与过渡支持。
步骤总结
- 计算公钥哈希:对公钥进行SHA-256和RIPEMD-160哈希,生成公钥哈希(pubKeyHash)。
- 创建见证程序:构建一个P2WPKH的见证程序,前缀是版本号0x00和长度0x14,加上生成的20字节公钥哈希。
- 哈希见证程序:对见证程序进行两次哈希,首先是SHA-256然后是RIPEMD-160。
- 添加P2SH前缀:将见证程序的RIPEMD-160哈希与P2SH前缀0x05拼接。
- 计算校验和:对拼接结果进行两次SHA-256哈希,并取前四个字节作为校验和。
- 拼接并Base58编码:将版本前缀、见证程序哈希和校验和拼接,最后进行Base58编码生成最终的地址。
代码示例:
ts 版本:
import { createHash } from 'crypto';
import bs58 from 'bs58';
export function generateNestedSegWitAddress(pubKeyHex: string): string {
// 将公钥的十六进制字符串转成字节数组
const pubKeyBytes = Buffer.from(pubKeyHex, 'hex');
// 对公钥进行 SHA-256 哈希运算
const sha256Hasher = createHash('sha256');
sha256Hasher.update(pubKeyBytes);
const pubKeySHA256 = sha256Hasher.digest();
// 对 SHA-256 哈希值进行 RIPEMD-160 哈希运算
const ripemd160Hasher = createHash('ripemd160');
ripemd160Hasher.update(pubKeySHA256);
const pubKeyHash = ripemd160Hasher.digest();
// 构建 P2WPKH 的见证程序(Witness Program)
// 其中 0x00 表示版本号,0x14 表示长度(20 字节)
const witnessProgram = Buffer.concat([Buffer.from([0x00, 0x14]), pubKeyHash]);
// 对见证程序进行 SHA-256 哈希运算
const witnessProgramSHA256Hasher = createHash('sha256');
witnessProgramSHA256Hasher.update(witnessProgram);
const witnessProgramSHA256 = witnessProgramSHA256Hasher.digest();
// 对 SHA-256 哈希值进行 RIPEMD-160 哈希运算
const witnessProgramRipemd160Hasher = createHash('ripemd160');
witnessProgramRipemd160Hasher.update(witnessProgramSHA256);
const witnessProgramHash = witnessProgramRipemd160Hasher.digest();
// 添加 P2SH 前缀
const version = Buffer.from([0x05]);
const versionedHash = Buffer.concat([version, witnessProgramHash]);
// 计算校验和
const firstSHA256 = createHash('sha256').update(versionedHash).digest();
const checksum = createHash('sha256')
.update(firstSHA256)
.digest()
.slice(0, 4);
// 拼接版本前缀、见证程序哈希和校验和
const fullHash = Buffer.concat([versionedHash, checksum]);
// 转换到 Base58
const address = bs58.encode(fullHash);
return address;
}
go 版本:
package utils
import (
"crypto/sha256"
"encoding/hex"
"github.com/btcsuite/btcutil/base58"
"golang.org/x/crypto/ripemd160"
)
func GenerateNestedSigwitddress(pubKeyHex string) (string, error) {
// 将公钥的十六进制字符串转成字节数组
pubKeyBytes, err := hex.DecodeString(pubKeyHex)
if err != nil {
return "", err
}
// 对公钥进行 SHA-256 哈希运算
hasherSHA256 := sha256.New()
hasherSHA256.Write(pubKeyBytes)
pubKeySHA256 := hasherSHA256.Sum(nil)
// 对 SHA-256 哈希值进行 RIPEMD-160 哈希运算
hasherRIPEMD160 := ripemd160.New()
hasherRIPEMD160.Write(pubKeySHA256)
pubKeyHash := hasherRIPEMD160.Sum(nil)
// 构建 P2WPKH 的见证程序(Witness Program)
// 其中 0x00 表示版本号,0x14 表示长度(20 字节)
witnessProgram := append([]byte{0x00, 0x14}, pubKeyHash...)
// 对见证程序进行 SHA-256 哈希运算
hasherSHA256.Reset()
hasherSHA256.Write(witnessProgram)
witnessProgramSHA256 := hasherSHA256.Sum(nil)
// 对 SHA-256 哈希值进行 RIPEMD-160 哈希运算
hasherRIPEMD160.Reset()
hasherRIPEMD160.Write(witnessProgramSHA256)
witnessProgramHash := hasherRIPEMD160.Sum(nil)
// 添加 P2SH 前缀
version := []byte{0x05}
versionedHash := append(version, witnessProgramHash...)
// 计算校验和
hasherDoubleSHA256 := sha256.New()
hasherDoubleSHA256.Write(versionedHash)
checksum := hasherDoubleSHA256.Sum(nil)
hasherDoubleSHA256.Reset()
hasherDoubleSHA256.Write(checksum)
checksum = hasherDoubleSHA256.Sum(nil)
finalChecksum := checksum[:4]
// 拼接版本前缀、赎回脚本哈希和校验和
fullHash := append(versionedHash, finalChecksum...)
// 转换到 Base58
address := base58.Encode(fullHash)
return address, nil
}
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。