UTXO
Bitcoin 采用了 UTXO 模型作为其底层存储的数据结构,其全称为 Unspent Transaction output,也就是未被使用的事务输出。
- Bitcoin客户端会在每次接收到新block时更新会所有未花掉的output,检查一个事务是否合法只需要看这个事务的所有input是否在这个未使用的output表中
- 输出存在已使用和未使用两个状态。BTC账本历史并不记录某个地址对应的BTC所,地址的总所是某一时刻与它关联的所有未使用输出的BTC数额之和。**所有未使用的事务输出(Unspent Transaction Outputs,UTXO)就构成了BTC这个区块链网络的一个状态。**网络中的(全功能)节点各自维护一个状态的副本,并在某一时刻就系统的状态达成最终一致。
- 一个输入同样也是一个列表,包含对之前一个未使用输出的引用,以及能够证明事务创建者满足该输出使用条件的有效签名。即证明事务创建者对他引用的未使用输出中BTC的使用权,被引用的输出会从UTXO中删除。一笔事务还要满足以下条件:输出所包含的总金额应小于等于输入所包含的总金额。这时输入与输出的差额作为事务奖励给记账的节点。满足以上输入与输出的事务被看作是“合法的”。
- 当一个地址要发布一笔事务时,它所做的其实是向BTC整个网络中的节点广播该条事务,该条事务会被标记为“未确认的”(Unconfirmed)。BTC网络并不是收到一条广播就立刻更新系统的状态,而是有区块以及内存池的设计。
在某一个时刻,所有BTC节点都维护着一个记录UTXO的账本,并有一个接收未确认事务的内存池(Mempool),当收到一条事务广播时,节点就会把该事务加入自己的内存池。
脚本
- 锁定脚本是放置在输出上的使用条件:它指定将来要使用输出必须满足的条件. scriptPubKey: 通常包含公钥或Bitcoin地址(公钥的哈希)
- 解锁脚本是可以“解决”或满足锁定脚本放置到输出上的条件,从而使用输出的脚本.大多数情况下,它们包含用户钱包利用私钥生成的数字签名. scriptSig:
- 每个Bitcoin验证节点通过一起执行锁定和解锁脚本来验证事务.只有正确满足输出条件的有效事务才会导致输出被视为“已使用”并从未使用的事务输出集和(UTXO集)中移除。
- 首先,使用堆栈执行引擎执行解锁脚本。如果解锁脚本没有错误地执行(例如,它没有遗留的“悬挂(dangling)”操作符),则复制主堆栈并执行锁定脚本。如果使用从解锁脚本复制的堆栈数据执行锁定脚本的结果为“TRUE”,则解锁脚本已成功解决由锁定脚本施加的条件,证明该输入是用于使用UTXO的有效授权。如果在执行组合脚本后仍然存在除“TRUE”之外的结果,则输入无效,因为它未能满足放置在UTXO上的消费条件。
- 请注意,事务的每个输入都是独立签署的。这是至关重要的,因为签名和输入都不必属于同一个“所有者”或被其使用。事实上,一个名为“CoinJoin”的特定事务方案利用这一事实来创建隐私的多方事务。
- 多方可以协作构建事务并各自签署一个输入。
创建数字签名
在Bitcoin的ECDSA算法实现中,被签名的“消息”是事务,或者更准确地说是事务中特定数据子集的哈希(参见下文 签名哈希的类型 (SIGHASH)
。签名密钥是用户的私钥。结果是如下签名:
((Sig = F_{sig}(F_{hash}(m), dA)))
其中:
-
dA 是签名私钥
-
m 是事务(或事务的一部分)
-
F__hash 是哈希函数
-
F__sig 是签名算法
-
Sig 是签名结果
生成签名 Sig ,由两部分组成: R 和 S: Sig = (R, S) , 再使用DER 的国际标准编码方案序列化为字节流。
签名的序列化 (DER)
R 和 S 的序列化字节流
序列化格式由以下九个元素组成:
-
0x30 —— 标识 DER 序列的开始
-
0x45 —— 序列长度 (69 bytes)
-
0x02 —— 接下来是一个整数
-
0x21 —— 整数的长度 (33 bytes)
-
R —— 00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb
-
0x02 —— 接下来是另一个整数
-
0x20 —— 另一个整数的长度 (32 bytes)
-
S —— 4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813
-
一个后缀 (0x01) 标识使用的哈希类型 (SIGHASH_ALL)
验证签名
签名验证算法采用消息(事务或其部分数据的散列),签名者的公钥和签名( R 和 S 值),如果签名对此消息和公钥有效,则返回TRUE。
签名哈希的类型 (SIGHASH)
有三种 SIGHASH 标志: ALL, NONE, 和 SINGLE
SIGHASH flag | Value | Description |
---|---|---|
ALL | 0x01 | 签名应用于所有输入和输出。 |
NONE | 0x02 | 签名应用于所有输入,不包括任何输出 |
SINGLE | 0x03 | 签名应用于所有输入,但仅应用于与签名输入具有相同索引编号的一个输出 |
BTC Transaction RAW
{
"version": 1,
"locktime": 0,
"vin": [
{
//一个事务ID,引用包含正在使用的UTXO的事务
"txid":"7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
//一个输出索引(vout),用于标识来自该事务的哪个UTXO被引用(第一个为零)
"vout": 0,
//一个 scriptSig(解锁脚本),满足放置在UTXO上的条件,解锁它用于支出
//大多数情况下,解锁脚本是一个证明Bitcoin所有权的数字签名和公钥,但是并不是所有的解锁脚本都包含签名
"scriptSig": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
//一个序列号
"sequence": 4294967295
}
],
"vout": [
{
//Bitcoin数额,最小单位为 聪 satoshis
"value": 0.01500000,
//定义了使用这些输出所需条件的scriptPubKey
"scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": 0.08450000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
}
]
}
数据结构定义
class CTransaction
{
const std::vector<CTxIn> vin;
const std::vector<CTxOut> vout;
//const int32_t nVersion;
//const uint32_t nLockTime;
}
class CTxIn
{
COutPoint prevout; //来自哪一笔事务的输出
CScript scriptSig; //签名
uint32_t nSequence;//用于锁定时间(locktime)或禁用 (0xFFFFFFFF)
}
class CTxOut
{
CAmount nValue; //所
CScript scriptPubKey; //持有当前输出的公钥
}
//--------------------------------------------------------------
class COutPoint
{
uint256 hash; //引用的事务id
uint32_t n; //第几条输出
}
//Serialized script
typedef prevector<28, unsigned char> CScriptBase;
class CScript : public CScriptBase
{}
typedef int64_t CAmount;
序列化
事务序列化 —— 输出
Size | Field | Description |
---|---|---|
8 字节 (小端序) | 数量 Amount | 以聪(satoshis = 10-8 bitcoin) 为单位的Bitcoin价值 |
1——9 字节 (VarInt) | 锁定脚本的大小 Locking-Script Size | 后面的锁定脚本的字节数 |
变量 | 锁定脚本 Locking-Script | 定义使用该输出的条件的脚本 |
事务序列化 —— 输入
Size | Field | Description |
---|---|---|
32 字节 | 事务的哈希值 Transaction Hash | 指向包含要使用的UTXO的事务的指针 |
4 字节 | 输出的索引 Output Index | 要使用的UTXO的索引,从0开始 |
1——9 字节 (VarInt) | 解锁脚本的大小 Unlocking-Script Size | 后面的解锁脚本的字节长度 |
变量 | 解锁脚本 Unlocking-Script | 满足UTXO锁定脚本条件的脚本 |
4 字节 | 序列号 Sequence Number | 用于锁定时间(locktime)或禁用 (0xFFFFFFFF) |
Gas
在每一笔合法的事务中,所有的输入的 value
之和必须大于所有输出的 value
之和,这两者之间的差值就是Gas:
sum(inputs.value) = sum(outputs.value) + fee
- Gas用是以事务数据的大小(KB)计算的,而不是Bitcoin事务的价值。
- 在Bitcoin Core中,中继策略由 minrelaytxfee 选项设置。当前的默认值是每KB数据0.00001Bitcoin或0.01毫Bitcoin。因此,默认情况下,低于0.00001Bitcoin的事务将被视为免费,并且只在内存池有空间时才会被中转;否则,它们将被丢弃。Bitcoin节点可以通过调整 minrelaytxfee 的值来覆盖默认的中继策略。
- 估算的API接口
curl https://bitcoinfees.earn/api/v1/fees/recommended
-
往期精彩回顾:
- 区块链知识系列
- 密码学系列
- 共识系列
- 公链调研系列
- 以太坊系列
- EOS系列
- 智能合约系列
更多推荐
BTC系列 - UTXO事务模型
发布评论