部分签名交易(Partially Signed Bitcoin Transaction, PSBT)是在 (BIP-174)[https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki] 中提出的一种交易格式,它使得用户可以对交易中的一部分输入进行签名,最后和其他用户的输入一起组成新的交易。这种交易格式的应用在于:

  • 离线钱包签名:观察签名进行交易的构造,随后由冷钱包完成签名,最后再广播到节点上,从而保证冷钱包的安全性;
  • 多方交易:多个签名者之间传输交易,最后组成新的交易;
  • NFT 市场:现有的铭文协议中,通常使用 PSBT 来构建买卖双方的交易,由 Dex 完成组装后完成双方的交易;

PSBT 规范

PSBT 的格式由一系列的键值对映射组成,键值对由 0x00 作为终止符,其二进制下的å格式如下[1]

1
<psbt> := <magic> <global-map> <input-map>* <output-map>*

依次对应每个值,有:

1
2
3
4
<magic> := 0x70 0x73 0x62 0x74 0xFF		// "PSBT" 的 ASCII + 0xFF 结尾
<global-map> := <keypair>* 0x00 // 一系列的键值对 keypair + 0x00 结尾
<input-map> := <keypair>* 0x00
<output-map> := <keypair>* 0x00

而键值对的组成如下:

1
2
3
<keypair> := <key> <value>				// key-value
<key> := <keylen> <keytype> <keydata> // keylen 是 keytype + keydata 的长度
<value> := <valuelen> <valuedata> // valuelen 是 valuedata 的长度

全局键值对、输入键值对、输出键值对定义了多种键类型(keytype),具体可参考 BIP-174 - Bitcoin Wiki

在所有签名者完成签名后,需要将所有的 PSBT 组合为一个 PSBT,完成该功能的角色被称为 Combiner,它最终会输出一个 PSBT,交由 Input Finalizer 进行处理

签名与签名类型

签名

在 Bitcoin 中,签名是 DER 格式的 ECDSA 签名,其签名以 0x30 标识符开头,在 DER 中表明这是一个组合的结构,然后跟一个表示结构长度的字节。这个组合结构就是 ECDSA 签名下的签名输出 $\sigma=(r,s)$

具体地,它的编码格式如下图所示[2],签名的格式实际上是对签名最终输出的 $(r,s)$ 进行二进制序列化

A diagram showing how convert a raw signature to DER encoding.

签名的过程根据 Pay-To 方式的不同划分为两种,分别用于非 Sigwit 交易和 Sigwit 交易,详见 Signature - Learn me a bitcoin 的 Legacy Algorithm 和 Segwit Algorithm 两种算法。Segwit 算法在 BIP-143 中被提出,它将交易数据划分为单独可重用的各个部分,便于加快交易的验证流程。

在 DER 签名的最后还有除签名本身之外的长为一个字节的 SIGHASH 字段,它用于标识签名的类型。

签名类型

以下内容需要分别对 Legacy 和 Segwit 签名算法中的“交易数据哈希 - Step.4”和“原像构造 - Step.1”过程有一定的了解才便于理解

在构造 Bitcoin 交易的过程中,签名必然需要针对某一交易进行签名,可以使用 $\sigma \leftarrow Sign(pk, m)$ 来表示这一过程,对交易数据进行哈希和构造原像就是为了得到这里的 $m$

构造哈希和原像的过程会得到一个待签名的消息 $m$,签名类型用于指定这个消息 $m$ 所关联的输入和输出,签名类型在签名完成后追加在 DER 格式的签名的最后作为签名的一部分。在 bitcoin 的代码库(bitcoin/src/script/interpreter.h)中定义了签名类型:

1
2
3
4
5
6
7
enum
{
SIGHASH_ALL = 1,
SIGHASH_NONE = 2,
SIGHASH_SINGLE = 3,
SIGHASH_ANYONECANPAY = 0x80,
};

前三种 SIGHASH_ALLSIGHASH_NONESIGHASH_SINGLE 指定关联了交易中的所有输入,分别指定不同的输出

image-20241013164852332

上图展示了前三种签名类型所关联的输入、输出,这三种签名类型都会和所有的输入关联,不同在于关联的输出。左侧和右侧分别代表了交易的输入和输出,红色的是当前待签名的输入,黄线则是签名所关联的输入或输出

  • SIGHASH_ALL:关联所有的输入和输出,这是面向消费者钱包的默认签名类型;
  • SIGHASH_NONE:仅关联所有的输入,但是和任何的输出都没有关系;
  • SIGHASH_SINGLE:关联所有的输入,同时关联一个和当前输入所在位置一样的输出,例如图中红色表示待签名的输入,那么关联的输出是对应位置上的输出;

image-20241013171531988

SIGHASH_ANYONECANPAY 和前面的三种签名类型一起使用(在位运算中通过按位或的方式来连接),但是它只用于指定当前签名的输入,这是部分签名实现所需要的基础

  • SIGHASH_ALL|SIGHASH_ANYONECANPAY:关联该输入的同时关联所有的输出,即在同意输出的情况下加入一笔交易;
  • SIGHASH_NONE|SIGHASH_ANYONECANPAY:仅关联当前的交易输入,是一种极不安全的签名类型。这代表输入可以被纳入到任何的交易,即允许这笔输入的比特币被任何人使用,可以被用作燃烧证明;
  • SIGHASH_SINGLE|SIGHASH_ANYONECANPAY:关联输入本身和对应位置上的输出,这是一种常用的部分签名交易。现在常被用于比特币上的交换协议中。例如在 Ordinals 协议下,输入中携带了一个 NFT 资产,那么需要交易的情况下可以指定该输入,并指定一个输出,要求在这个输出下向自己的地址发送一定数量的 btc。这样的部分签名就可以用于实现交易市场,这也是目前的大部分交易市场实现的原理;