一文详解比特币的Nested SegWit (P2SH)地址类型

  • Louis
  • 更新于 2024-10-18 10:03
  • 阅读 907

基本概念:比特币的P2SH地址类型(Pay-to-Script-Hash,支付到脚本哈希)是一种常见的比特币地址类型,用于支持更复杂的交易条件。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
}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis