比特币中对交易进行签名的详细过程

简介: 最近在和同事交流我们PalletOne中对UTXO和签名的处理,有些心得,写下此博文。对比特币有点基本概念的都知道,比特币是通过ECDSA数字签名来解锁UTXO中的未花费余额。 关于UTXO我不需要做太多介绍,毕竟介绍这个概念的文章已经很多了。

最近在和同事交流我们PalletOne中对UTXO和签名的处理,有些心得,写下此博文。对比特币有点基本概念的都知道,比特币是通过ECDSA数字签名来解锁UTXO中的未花费余额。

关于UTXO我不需要做太多介绍,毕竟介绍这个概念的文章已经很多了。我主要是谈谈已经有UTXO了,该怎么花掉。

交易的结构

我们先来看看在比特币中,一个交易的结构是什么样的?

type MsgTx struct {

    Version int32

    TxIn []*TxIn

    TxOut []*TxOut

    LockTime uint32

}

type TxOut struct {

    Value int64

    PkScript []byte

}

type TxIn struct {

    PreviousOutPoint OutPoint

    SignatureScript []byte

    Sequence uint32

}

type OutPoint struct {

    Hash chainhash.Hash

    Index uint32

}

我们可以看到,一个交易(MsgTx)是由多个Input和多个Output组成的,而在Input中是由指向UTXO的OutPoint,解锁脚本SignatureScript和序列Sequence组成。

UTXO我们可以认为是一个KeyValue的大表,在该表中,交易的Hash和该交易中Output所在的位置索引Index就构成了UTXO的Key,而Value就是比特币Amount、锁定脚本等信息,所以在UTXO数据库中,我们通过OutPoint能够很快的找到对应的Amount和锁定脚本。

在比特币中,要做一笔交易分为三个步骤:

  1. 构建原始交易RawTransaction,该交易包含了输入指向的OutPoint,也包含了完整的Output,但是没有签名,也就是没有设置SignatureScript的内容。
  2. 用私钥对签名构建的RawTransaction进行签名,并将签名构建成完整的解锁脚本,填入对应的Input的SignatureScript字段中。
  3. 将签名后的Transaction发送到P2P网络中。

构建原始交易RawTransaction

现在假设我有一个地址mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV(这是一个测试网地址),该地址收到了两笔转账,一笔0.4BTC(https://testnet.blockchain.info/tx-index/239152566/1),另一笔1.1BTC(https://testnet.blockchain.info/tx-index/239157459/1),这两笔收入都是在其交易Output的第二条,也就是Index=1(Index从0开始算)。现在我们想要做一笔1.2BTC的转账,然后给一定的手续费后,找零到原地址,所以我们会构建一笔交易,该交易有2Input和2Output。

以下是我用Go基于btcd写的示例代码,这里我们就构建好了一个RawTransaction。

func buildRawTx() *wire.MsgTx {

//https://testnet.blockchain.info/tx/f0d9d482eb122535e32a3ae92809dd87839e63410d5fd52816fc9fc6215018cc?show_adv=true

tx := wire.NewMsgTx(wire.TxVersion)

//https://testnet.blockchain.info/tx-index/239152566/1 0.4BTC

utxoHash, _ := chainhash.NewHashFromStr("1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52")

point := wire.OutPoint{Hash: *utxoHash, Index: 1}

//构建第一个Input,指向一个0.4BTC的UTXO,第二个参数是解锁脚本,现在是nil

    tx.AddTxIn(wire.NewTxIn(&point, nil, nil))

//https://testnet.blockchain.info/tx-index/239157459/1 1.1BTC

utxoHash2, _ := chainhash.NewHashFromStr("24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66")

point2 := wire.OutPoint{Hash: *utxoHash2, Index: 1}

//构建第二个Input,指向一个1.1BTC的UTXO,第二个参数是解锁脚本,现在是nil

    tx.AddTxIn(wire.NewTxIn(&point2, nil, nil))

//找零的地址(这里是16进制形式,变成Base58格式就是mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV)

pubKeyHash, _ := hex.DecodeString("b5407cec767317d41442aab35bad2712626e17ca")

lock, _ := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160).

AddData(pubKeyHash).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).

Script()

//构建第一个Output,是找零0.2991024 BTC

    tx.AddTxOut(wire.NewTxOut(29910240, lock))

//支付给了某个地址,仍然是16进制形式,Base58形式是:mxqnGTekzKqnMqNFHKYi8FhV99WcvQGhfH。

pubKeyHash2, _ := hex.DecodeString("be09abcbfda1f2c26899f062979ab0708731235a")

lock2, _ := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160).

AddData(pubKeyHash2).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).

Script()

//构建第二个Output,支付1.2 BTC出去

    tx.AddTxOut(wire.NewTxOut(120000000, lock2))

return tx

}

交易的签名过程

现在我们知道私钥,需要对该交易进行签名,因为有2个Input,所以我们要签名2次,每个签名的原理是一样的,我就以第一个Input为例来说明吧。

在比特币中,对一笔交易的签名流程是这样的:

1.查找该笔交易对应的UTXO

2.获得该UTXO对应的锁定脚本

3.复制该交易对象,并在复制副本中将该Input的解锁脚本字段的值设置为对应的锁定脚本

4.清除其他Input的解锁脚本字段

5.对这个改造后的交易对象计算Hash

6.使用私钥对Hash进行签名。

用表格的形式可以更容易表达:

这是原始未签名的交易RawTransaction,主要是第二列和第三列:

UTXO Input Output

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1,

Amount:0.4BTC,PkScript:

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1,

Amount:1.1BTC,PkScript:

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

接下来我们要对第一个Input签名,于是我们需要将交易复制一个副本,并改为:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =

OP_DUP OP_HASH160 PUSHDATA(20) b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

接下来对这个交易计算Hash,然后进行签名。得到签名结果:3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd22ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501 而我们知道这个地址的公钥是:038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb

所以签完名后,我们的交易变成:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =

PUSHDATA(72)[3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd2

2ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]

,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

这才只是完成了第一个Input的签名,接下来我们再对第二个Input进行签名,同样的道理,我们需要制造一个交易的副本,然后把第一个Input的SignatureScript清空,然后给第二个Input的SignatureScript赋值:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =OP_DUP OP_HASH160 PUSHDATA(20) b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

显然这个副本与第一个签名时的数据是不一样的,所以签名结果也不一样,最终签名结果为:30440220196bce75f0a25ac8afa7218aefc86cba3924845450f3d311c89e9c2a3438a99c0220230bed598a610be971ca49690f4b42ac2acfa80c09d4cbabd278b03c824af14501,当然我们因为是同一个地址,所以公钥是一样的:038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb

我们把这个签名和公钥再放回原始交易中,就变成我们需要的完整签名的交易:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =PUSHDATA(72)[3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd22ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]

,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =PUSHDATA(71)[30440220196bce75f0a25ac8afa7218aefc86cba3924845450f3d311c89e9c2a3438a99c0220230bed598a610be971ca49690f4b42ac2acfa80c09d4cbabd278b03c824af14501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]

,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

这就是一个真实完整的交易了,接下来就可以通过P2P网络发送该交易,并最终被矿工打包确认。

总结

实际上在比特币的源码中比我上面说的还要复杂一些,还涉及到这个hash是对整个交易进行SigHashAll还是SigHashSingle或者SigHashNone,这些都是很特殊的情况,一般的比特币钱包也不支持,具体可以参加精通比特币书中的介绍:6.5.3签名哈希类型( SIGHASH)

普通来说,我们要对一笔交易进行签名或者验签,就是把当前Input中的解锁脚本替换成锁定脚本,而其他Input的解锁脚本情况,然后计算Hash和签名!

其实我还是有点不明白,为什么比特币中不直接对没有任何解锁脚本的RawTransaction进行签名呢?而是非要加上锁定脚本来签名?不知道这里面有什么更深的考虑。

【本文章出自 博客园深蓝居,转载请注明作者出处,如果您觉得博主的文章对您有很大帮助,欢迎点击右侧打赏按钮对博主进行打赏。】
打个招聘广告,博主正在主导开发一个跨链区块链项目:PalletOne,一直在招Go程序员,待遇丰厚,坐标北京酒仙桥,希望有识之士加入!
目录
相关文章
|
5月前
|
人工智能 自然语言处理 Java
Java做AI真不行?2026年最被低估的机会来了
Spring官宣集成DeepSeek,Java正式迈入AI驱动时代!2026年AI岗位缺口巨大,大厂招聘普遍要求大模型能力。Java团队借力Spring生态与JBoltAI等国产框架,可低门槛接入代码生成、RAG、Agent等全链路AI能力,实现差异化突围。(239字)
484 3
|
6月前
|
弹性计算 安全 双11
阿里云2026优惠券领取与使用:学生300元无门槛,个人企业满减券,企业迁云与出海补贴详解
2026年阿里云推出多样化优惠券活动,包括学生300元无门槛券、企业迁云补贴与出海扶持补贴、新用户满减券等。学生需完成认证领取300元无门槛券,适用于多数公共云产品;企业用户可申请迁云补贴(最高达5亿元)和出海扶持补贴(3500元至10万元不等);新用户可在大促期间领取满减券,用于新购或升级订单。用户可在阿里云控制台管理优惠券,并在购买云产品时自动匹配抵扣。
1080 1
|
程序员 开发工具 Windows
编程必备,程序员应该都知道的7款文本编辑器
正如一个作家需要一个文字处理器来写故事,一个艺术家需要画布来创作,同样的,如果想编程,你会需要一个地方来写代码。程序员在哪里编写代码?最常见的就是使用文本编辑器了吧。下文列出了 7 个主流的文本编辑器,不出意外的话,开发人员应该都有所了解,至少听说过。7款文本编辑器,总有一款会适合你。
11252 114
|
供应链 安全 算法
区块链技术探索与应用:从密码学奇迹到产业变革引擎
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。以代码为舟,算法为帆,在区块链的浩瀚星河中探索去中心化的未来。从智能合约到DeFi,用极客精神谱写信任新篇章。
区块链技术探索与应用:从密码学奇迹到产业变革引擎
|
存储 算法 安全
即时通讯安全篇(三):一文读懂常用加解密算法与网络通讯安全
作为开发者,也会经常遇到用户对数据安全的需求,当我们碰到了这些需求后如何解决,如何何种方式保证数据安全,哪种方式最有效,这些问题经常困惑着我们。52im社区本次着重整理了常见的通讯安全问题和加解密算法知识与即时通讯/IM开发同行们一起分享和学习。
665 9
|
算法 安全 网络安全
概念区分:对称加密、非对称加密、公钥、私钥、签名、证书
概念区分:对称加密、非对称加密、公钥、私钥、签名、证书
4140 0
|
存储 负载均衡 算法
Hash介绍与应用详解
哈希算法在计算机科学中有着广泛而重要的应用,从数据存储、数据完整性校验到密码安全和分布式系统中的负载均衡,哈希函数都发挥着关键作用。通过本文的介绍和示例代码,希望您能更好地理解哈希的基本概念和实际应用,并在您的项目中有效地应用这些知识。
3701 3
|
存储 缓存 Java
【数据结构】哈希表
【数据结构】哈希表
403 2
|
开发框架 JavaScript 前端开发
2024年全面且功能强大的.NET快速开发框架推荐,效率提升利器!
2024年全面且功能强大的.NET快速开发框架推荐,效率提升利器!
1498 0
|
安全 数据安全/隐私保护
深入解析 https
在使用HTTP协议时,数据传输是明文形式,容易遭受运营商劫持等安全问题,如篡改返回网页内容、修改Referer字段等。为解决这些问题,引入了HTTPS协议,它通过加密、认证和完整性保护,确保通信内容不被第三方窃听或篡改。HTTPS结合了对称加密和非对称加密,使用公钥加密对称密钥,私钥解密,确保数据安全性和传输效率。然而,中间人攻击仍可能破解这一机制,因此引入证书机制,客户端通过验证证书中的数字签名来确认公钥的有效性,从而保障数据传输的安全性。
673 0

热门文章

最新文章