比特币地址与签名全解:从P2PKH到Taproot与PSBT

📅 发布时间:2026/10/13 4:09:11
比特币地址与签名全解:从P2PKH到Taproot与PSBT
说到比特币地址类型和签名方式这几年真的可以用“三年一小变五年一大变”来形容。早期做开发只需要记住1开头和3开头两种地址后来隔离见证把bc1q带到台前再后来Taproot又贡献了一个bc1p。很多人写钱包或者做离线签名时最容易卡住的不是私钥管理而是地址背后的脚本模板没对上明明签名生成了广播后却被打回或者收款地址看着没问题最后手续费却比预期高不少。这篇文章就是一份给开发者和进阶用户的总结覆盖地址前缀、编码规则、脚本模板、ECDSA与Schnorr签名、多签与PSBT协作最后放一些实际排坑经验。你可以把它当速查手册也可以顺着章节把底层逻辑完整走一遍。1. 四种地址类型先看清1开头、3开头、bc1q、bc1p1.1 地址不是账户而是收款条件的指纹比特币地址看起来像一串随机字符但它本质上不是一个“账户”也不是“钱包ID”。准确地说地址是一种锁定脚本的可读编码。别人给你付款时链上写入的是一个脚本而不是“往某个账户里加钱”将来你要花费这笔钱必须提供一组能通过这个脚本验证的数据通常是公钥与签名。把地址理解为“解锁条件的指纹”会更准确。同样的私钥如果使用不同的脚本模板就会得到完全不同的地址。这也是很多初学者困惑的地方同一把私钥既能生成1开头的地址也能生成bc1q、bc1p开头的地址它们背后对应着不同的锁定脚本。理解这一点后面所有内容都会顺很多。1.2 主网地址类型速查表我先把最常碰到的几类主网地址放在一张表里方便随时对照地址类型地址前缀编码方式版本字节 / 见证版本锁定脚本模板典型用途P2PKH1Base58Check版本字节 0x00OP_DUP OP_HASH160 20字节 OP_EQUALVERIFY OP_CHECKSIG经典单签P2SH3Base58Check版本字节 0x05OP_HASH160 20字节脚本哈希 OP_EQUAL多签、嵌套隔离见证P2WPKHbc1qBech32隔离见证 v020字节程序OP_0 20字节原生单签隔离见证P2WSHbc1qBech32隔离见证 v032字节程序OP_0 32字节隔离见证多签/复杂脚本P2TRbc1pBech32m隔离见证 v132字节程序OP_1 32字节公钥Taproot单签/多签/脚本路径这张表值得多看两遍。注意最后一列“典型用途”并不是强制约束P2SH可以装多签也可以装其他可证明的脚本P2WSH也一样。真正区分类型的是中间那几列编码与锁定脚本模板。1.3 地址类型演变的时间线为什么会有这么多地址类型因为每一代都在解决前一代的某些具体问题。最早期的交易形态是“支付给公钥”也就是P2PK。公钥本身很长地址可读性差也不利于隐私。后来出现了P2PKH把公钥先做Hash160再放进脚本地址因此缩短到以1开头。P2SH出现则是为了解决“复杂脚本不好用”的问题。比如要让付款方给一个2-of-3多签地址转账如果对方必须拿到一整段完整的赎回脚本体验和安全性都很差。P2SH允许付款方只看到20字节的脚本哈希3开头的地址由此诞生。隔离见证是下一个里程碑。它的核心改动是把签名等见证数据从交易的哈希计算范围中移出去带上了“见证数据打四分之一折扣”的激励。P2WPKH和P2WSH因此被设计出来地址使用Bech32编码以bc1q开头。之后还有一种过渡形态叫嵌套隔离见证也就是P2SH-P2WPKH、P2SH-P2WSH它们仍然以3开头但实际使用隔离见证的验证逻辑。Taproot则是在2021年前后正式落地的升级。它引入Schnorr签名支持密钥路径与脚本路径两种花费方式并让多签在链上看起来和单签几乎一样。对应的地址类型就是P2TR以bc1p开头。理解了这条时间线你会发现自己不是在背一堆孤立的名字而是在看一项技术如何一步步补上此前的短板。1.4 最容易搞混的一个点P2WPKH与P2WSH的区别很多人看到bc1q就默认它是单签地址这个想法在普通钱包场景里偶尔成立但从协议层面看并不准确。P2WPKH和P2WSH的地址前缀完全一样都是bc1q开头区别在于解码后得到的程序长度P2WPKH是20字节P2WSH是32字节。当你面对一个bc1q地址时人眼是无法可靠区分它到底是单签还是多签的必须用解码库去解析。开发中如果只用正则去匹配“地址是不是bc1q开头”很容易把P2WSH当成P2WPKH处理后续签名和费用估算都会出错。2. 地址编码与校验Base58Check、Bech32与Bech32m背后的事2.1 为什么地址一定要带校验比特币地址是给人复制和输入的但人不是完美机器漏掉一个字符、打错一个字母都可能导致资金进入一个无法花费的地址。校验码就是最后一道防线。Base58Check用双SHA256前4字节作为校验Bech32和Bech32m则使用更复杂的BCH码目的都是让常见错误大概率被发现而不是把钱打到无人能取的地方。有人会问Base58Check只有4字节校验够吗对于抄写错误来说32位的校验已经能挡住绝大多数情况。真正需要小心的反而是“看起来合法但并不属于你”的地址以及测试网地址与主网地址混用这些靠校验码无法完全解决。2.2 Base58Check版本字节加双SHA256Base58Check的字符集去掉了0、O、I、l这四个容易混淆的字符。地址结构可以拆成三部分版本字节、数据、校验码。其中校验码是双SHA256结果的前4字节最后整体做Base58编码。主网P2PKH的版本字节是0x00所以地址以1开头主网P2SH的版本字节是0x05所以地址以3开头。测试网又是另一组版本字节。因此地址在解码后能直接告诉你它属于哪个网络、是哪一类脚本。下面是一段示意解码逻辑# 示意Base58Check 解码 def b58decode_check(s): raw base58_decode(s) payload, checksum raw[:-4], raw[-4:] if hash256(payload)[:4] ! checksum: raise ValueError(checksum mismatch) return payload[0], payload[1:] # version, data版本字节的作用比很多人以为的更大。它不仅标识类型还承担了“网络隔离”的一部分职责。如果你把一个测试网地址当主网地址广播节点层面并不认识即使链上侥幸打包资金也会进入一个无法由你花费的脚本上下文。2.3 Bech32与Bech32m不是简单的升级关系Bech32编码最早用于隔离见证v0地址也就是bc1q开头的地址。它的HRP人类可读前缀是bc后面用数字1作为分隔符数据部分按5位一组编码。Bech32地址要求要么全小写要么全大写不能混合大小写实际使用时基本统一采用小写。Bech32m则是为了SegWit v1及后续版本准备的P2TR的bc1p地址就使用Bech32m。两者的校验算法相同只是最后的校验常数不同。为什么要单独设计一个Bech32m因为如果v1地址继续沿用Bech32某些特定篡改错误可能无法被检测出来而且旧的解码逻辑也容易把新地址误判成v0。校验常数一换两个时代的数据就自然分开了。如果你遇到一个bc1p地址而后端解码库仍然只实现了Bech32而没有实现Bech32m解码会直接报校验失败。这不代表地址无效而是库版本需要升级。处理这类问题要先看钱包或后端代码对BIP350的支持情况。2.4 从地址还原出锁定脚本的流程地址解析并不是“看前缀猜类型”这么简单严谨的做法是走一遍解码流程根据前缀判断属于Base58Check还是Bech32/Bech32m体系。对Base58Check地址先解码并校验取出版本字节和数据版本字节决定脚本模板。对Bech32/Bech32m地址先校验HRP是否为bc再取出见证版本和程序数据见证版本和程序长度共同决定是P2WPKH、P2WSH还是P2TR。# 示意地址转锁定脚本 def addr_to_script(addr): low addr.lower() if low.startswith((1, 3)): ver, data b58decode_check(addr) if ver 0x00: return OP_DUP OP_HASH160 data OP_EQUALVERIFY OP_CHECKSIG if ver 0x05: return OP_HASH160 data OP_EQUAL elif low.startswith(bc1): hrp, witver, prog bech32_decode(addr) if witver 0: return OP_0 prog # v020字节为P2WPKH32字节为P2WSH if witver 1: return OP_1 prog # v1P2TR实际工程中不需要自己实现这些编码成熟的开源库已经封装好但理解流程对排查问题非常有用。很多“为什么付款方说我地址不对”的问题最后都出在解码环节有人用错了版本有人把bc1p放进了老式Bech32解码器有人把大小写混在一起导致校验直接不过。3. 签名方式演进ECDSA、低S值与Schnorr的取舍3.1 ECDSA至今仍是主流的椭圆曲线签名比特币早期采用的签名算法是ECDSA使用的椭圆曲线是secp256k1。ECDSA签名由两个整数r和s组成编码成DER格式后通常有70到72字节。我们平时见到的P2PKH、P2SH、P2WPKH、P2WSH只要它们的花费条件是“提供公钥和签名”本质都是ECDSA签名。理解ECDSA的关键在于它能证明你拥有某个私钥而不需要把私钥本身暴露出来。验证方拿到公钥、消息哈希和签名就能完成校验。对于开发者来说更重要的一点是生成签名时随机数k必须安全且唯一。随机数一旦重用或者随机数可被预测私钥就可能被直接推导出来。这不是理论风险链上曾出现过因此泄露私钥的真实案例。3.2 延展性问题与低S值ECDSA有一个著名特性签名并不唯一。同一个私钥对同一个消息可以构造出不同的(r, s)对而且都通过验证。第三方虽然无法伪造出“别人私钥的有效签名”但可以在不改变交易语义的情况下修改签名形式进而改变交易ID。在隔离见证出现之前这种签名延展性给上层业务带来不少麻烦。很多系统依赖交易ID作为状态流转的依据结果广播前被第三方改了一下签名交易ID就变了业务状态就对不上了。BIP146提出了低S值规则让签名形式尽量标准化减少延展性空间。隔离见证则从结构上解决了这个问题见证数据不再参与交易ID的计算签名怎么变交易ID都不受影响。这也是隔离见证带来的一个容易被忽略的重要好处。3.3 Schnorr为什么Taproot要换算法Schnorr签名BIP340在比特币里主要服务于Taproot。它最吸引人的特性是“线性”多个公钥可以聚合多个签名也可以聚合。这就意味着一个由多人共同控制的多签地址在链上花费时可以只表现为一个聚合公钥加上一个聚合签名看起来和普通单签几乎一样。隐私和手续费都能得到改善。Schnorr签名的长度是固定的64字节比ECDSA的DER编码更短更规整。它还支持批量验证多个签名可以放在一起做一次批量校验显著提升验证效率。Schnorr同样需要安全的随机数尤其是聚合签名场景下nonce的交互方式会影响安全性所以后来又衍生出MuSig等密钥聚合协议。普通开发者不需要自己实现这些但至少要知道选择P2TR不代表“自动获得多签聚合”具体还需要钱包协议层面的配合。3.4 签名算法不是随便选的由脚本决定对比项ECDSASchnorrBIP340签名长度DER编码约70-72字节固定64字节第三方延展性存在低S值缓解基本不存在聚合能力不支持直接聚合支持密钥与签名聚合批量验证无支持主要使用场景P2PKH/P2SH/P2WPKH/P2WSHP2TR及Tapscript这里要特别强调一个容易混淆的点签名方式不是钱包自己“选一个好看的”就行而是由锁定脚本决定的。一个P2TR输出被花费时验证脚本走的是Schnorr路径如果你对这个输出强行使用ECDSA签名它根本过不了验证。反过来P2PKH输出的花费脚本里写的是OP_CHECKSIG十六进制脚本的执行环境也只认识ECDSA签名。所以在排查问题时先看地址对应的脚本类型再判断签名算法是否匹配顺序不能反。4. 多签场景地址类型如何影响脚本与协作签名4.1 OP_CHECKMULTISIG与多签模板比特币多签最常见的形式是m-of-n脚本模板大致如下m 公钥1 公钥2 ... 公钥n n OP_CHECKMULTISIG2-of-3就是m2、n3表示三个公钥里至少两个提供有效签名才能花费。这个模板在P2SH、P2WSH和Taproot里都有应用但呈现方式不同。OP_CHECKMULTISIG存在一个历史包袱执行时它会从栈上多弹出一个元素。所以花费方提供解锁数据时必须在签名前面再塞一个空元素或0否则脚本验证会失败。另一个经典坑是签名顺序必须严格对应脚本里公钥的顺序。如果你在2-of-3里用第二个和第三个公钥对应的私钥签名但把签名顺序写反了OP_CHECKMULTISIG照样不认。这个和“有足够数量的签名就行”的直觉完全不同很多刚接触多签的工程人员都在这里栽过。4.2 P2SH、P2WSH、P2TR多签差异在哪同样是2-of-3多签放在不同地址类型里链上表现和费用差别很大。P2SH多签地址以3开头锁定脚本只保存整个多签脚本的20字节哈希多签脚本本身叫赎回脚本。付款方完全不知道收款方内部是多签还是其他条件。花费时签名方必须公开赎回脚本并提供对应数量的签名。交易体积大隐私一般。P2WSH多签地址以bc1q开头但程序长度是32字节。多签脚本以见证脚本的形式参与验证。由于见证数据有折扣交易体积比P2SH多签明显下降费用更低。P2TR多签地址以bc1p开头有两条花费路径。第一条是密钥路径需要把多个参与方的公钥通过聚合协议聚合成一个内部公钥花费时提供一个聚合签名即可链上表现接近单签。第二条是脚本路径可以把多签条件作为Taproot脚本树里的一个叶子花费时揭示脚本和控制块验证方执行里面的多签逻辑。两者各有取舍密钥路径更隐私、更省费但要求所有参与方配合聚合协议脚本路径更灵活但脚本本身会暴露在链上。4.3 PSBT让不同签名者协作的标准格式多签项目里签名的人不在一起签名顺序也不固定。这时候PSBT就非常关键。PSBT的中文可以理解成“部分签名比特币交易”它是一套标准格式让多个签名者可以轮流对同一笔拟交易补充信息你签完传给我我再签最后合并成完整交易。地址类型直接影响PSBT需要携带的字段。非隔离见证输入比如P2PKH或P2SH多签签名者通常需要non_witness_utxo也就是完整的UTXO交易以便确认要花的钱确实存在且金额准确。隔离见证输入通常只需要witness_utxo但嵌套隔离见证比如P2SH-P2WPKH或P2SH-P2WSH还需要redeemScriptP2WSH则还需要witnessScript。如果这些上下文信息缺失签名设备会拒绝签名或者只能生成一种带警告的部分签名。4.4 一个实际协作案例我接到过一个很典型的排查请求一个2-of-3多签地址两位参与者签得很顺利第三位参与者始终拒绝签名而且不报具体原因。把未完成交易导出后对比发现PSBT输入里只有witness_utxo和witnessScript缺少了外层P2SH的redeemScript。第三位参与者的钱包需要完整的脚本上下文才能算出正确的签名摘要缺失关键字段后只能选择拒绝。补上redeemScript字段后签名立刻正常。这类问题在PSBT协作里特别普遍共同点是签名算法本身没有错错在“上下文信息不完整”。所以排查多签问题时不要一上来怀疑私钥或算法先把PSBT字段清单过一遍。5. 实操选型手续费、兼容性、备份与排坑经验5.1 地址类型对手续费的影响比特币手续费不是由“转账金额”决定而是由交易占用的区块字节大小决定。隔离见证引入的权重规则是普通数据每个字节按4权重单位计算见证数据每个字节按1权重单位计算最终交易权重再除以4得到vsize。下面是一组主网单签输入的常见估算值地址类型单签输入估算P2PKH约148 vBytesP2WPKH约68 vBytesP2TR密钥路径约58 vBytes实际签名长度会因DER编码产生几字节浮动但量级差距非常明显。P2PKH输入几乎是P2WPKH的两倍多所以同样的交易内容用1开头地址付款和用bc1q地址付款手续费可能差出一大截。交易输出也有差异P2PKH输出约34字节P2WPKH输出约31字节P2TR输出约43字节。输出数量多的时候P2TR的地址长度优势会被抵消不少这一点在批量支付场景里尤其要注意。5.2 兼容性先问接收方支持什么地址类型没有绝对的“最好”只有“最合适”。个人收款优先选P2WPKH或P2TR省费且隐私更好。但如果你是面向商户或老旧系统的开发者仍然会遇到只支持1开头和3开头地址的支付网关。也就是说接收方钱包不支持Bech32时强行给对方一个bc1q地址结果通常是付款失败而不是“自动转换”。这里要特别提醒永远不要试图通过字符替换或“格式转换”把3开头地址改成bc1q地址。地址是锁定脚本的编码改了地址就等于把资金锁到一个完全不同的脚本上。正确做法是向接收方索要它支持的地址类型或者让钱包在设置里切换地址格式。5.3 地址复用与隐私无论什么地址类型同一个地址多次收款都会降低隐私。链上任何人都能看到这个地址收到了多少笔钱、分别是什么时间、大概从哪里来。把多笔资金汇聚到同一个地址等于亲手把资金关系图谱送给观察者。正确习惯是每个收款场景使用新地址这也是很多钱包默认行为。还有一点容易被忽略地址不是余额容器。比特币的“余额”只是钱包对UTXO集合的统计结果一个地址可能关联多个UTXO也可以被多个地址共同构成一笔“余额”。当你在代码里做余额计算时脑子里要有UTXO模型而不是传统的账户模型。5.4 描述符备份别只保存地址只保存地址列表是远远不够的尤其是多签和复杂脚本场景。P2SH多签的赎回脚本、P2WSH的见证脚本、P2TR的脚本树结构如果丢失即使备份了私钥也可能无法构造出正确交易。输出脚本描述符就是为了解决这个问题而存在的。描述符用一段文本同时表达脚本类型、密钥和派生路径例如wpkh([fingerprint/84/0/0]xpub.../0/*)这类格式。文件里写下描述符和对应的主私钥或扩展公钥恢复钱包时就不需要猜测脚本上下文。我在实际恢复案例中体会很深只记录了几个地址和私钥却没有保存赎回脚本最后只能靠遍历和猜测才把P2SH多签构造出来非常痛苦。5.5 排错顺序从地址到签名逐步验证遇到“交易被拒”的问题时我建议按下面顺序排查先解码地址确认它到底是哪一类脚本。这一步能消除一半以上的“类型猜错”。确认UTXO确实存在并且它的锁定脚本和本次构建脚本一致。确认签名算法与锁定脚本匹配P2PKH用ECDSAP2TR用Schnorr。确认sighash类型一致比如SIGHASH_ALL、SIGHASH_SINGLE等。确认PSBT字段完整特别是多签和嵌套隔离见证场景。广播前用本地节点或公共接口的testmempoolaccept做一次预检而不是直接广播一个“看起来没问题”的交易。跳过第1步或者第3步是最常见的错误来源。很多开发者拿到一个bc1p地址先按P2WPKH去算脚本和费用结果自然不对。把“地址解码”当成调试的第一步你会发现大部分问题自己就现形了。6. 收尾我最想强调的几点经验我在实际排查中见过太多因为搞混地址类型导致的资金卡死归根到底都是没把“地址是脚本的编码”这个意识建立起来。如果你刚开始接触只要记住地址管的是“钱被什么条件锁定”签名管的是“怎么证明你有权解锁”两者通过scriptPubKey对账。遇到问题时先把地址解码成脚本再看看签名用的算法和sighash90%的问题都能定位。另一条经验是不要试图把地址类型和签名方式当成两张独立的表去背。它们本来就是同一件事的两个侧面。1开头对应P2PKH3开头对应P2SHbc1q对应隔离见证v0bc1p对应TaprootECDSA负责隔离见证之前的绝大多数签名Schnorr负责Taproot主路径。顺着隔离见证和Taproot这两次升级去理解比死记硬背每个版本的字节常量要高效得多。真到需要抠细节的时候再按BIP编号查原始文档也不迟。