Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

V刘晨曦
96 0 0
交易的结构
% u) L2 c1 _& W& F" J" r我们先来看看在比特币中,一个交易的结构是什么样的?3 X9 G: B, I. {
type MsgTx struct {' Y7 }) R, g' f, ?7 C4 D. P4 Q
    Version int322 j* ~% M7 G9 o% @+ B: q
    TxIn []*TxIn
. ~" ?6 ?$ M1 e8 X/ X- `9 ]. D    TxOut []*TxOut
( T1 s0 j0 a+ f1 k! T2 `    LockTime uint321 @  b0 B: a4 ?& L( p( r9 _( \
}. u$ m7 k- b8 E. M% _6 N+ }5 ^
type TxOut struct {  U4 q0 D7 X0 b7 @- H& U. x
    Value int64
/ x) J0 G, ?$ X) e/ a( V" H- s    PkScript []byte
; ~6 X; D% I$ O/ _) H# C}- ?- X) a4 y. E# c% R
type TxIn struct {: s" t9 g8 ^2 T- x" ^% J4 y
    PreviousOutPoint OutPoint+ t  j0 h8 C4 }# L% R  j
    SignatureScript []byte
+ H) M# N. y) Z. I% ^" [  |    Sequence uint32
4 B: g& a' W+ g+ x}
$ a9 K3 w) V  z' {# C$ t# a7 xtype OutPoint struct {
" V/ ^" w4 m: k- S7 H; c    Hash chainhash.Hash& V0 q0 Q4 Y$ a: B  u, F( n
    Index uint32$ B- I1 K& g' G; C, |9 M
}
+ B) {& r2 [$ y! [" k我们可以看到,一个交易(MsgTx)是由多个Input和多个Output组成的,而在Input中是由指向UTXO的OutPoint,解锁脚本SignatureScript和序列Sequence组成。- ?. o0 ~( I% _6 C0 v
UTXO我们可以认为是一个KeyValue的大表,在该表中,交易的Hash和该交易中Output所在的位置索引Index就构成了UTXO的Key,而Value就是比特币Amount、锁定脚本等信息,所以在UTXO数据库中,我们通过OutPoint能够很快的找到对应的Amount和锁定脚本。
2 ]5 t. v& _7 {在比特币中,要做一笔交易分为三个步骤:
: m# j5 Q6 o1 v* q# \3 q
" Q/ {4 @. D1 K$ O  O" Y[*]构建原始交易RawTransaction,该交易包含了输入指向的OutPoint,也包含了完整的Output,但是没有签名,也就是没有设置SignatureScript的内容。$ F, P. ~1 s, Y: P6 Y' x: R
[*]用私钥对签名构建的RawTransaction进行签名,并将签名构建成完整的解锁脚本,填入对应的Input的SignatureScript字段中。
! Z; @9 q! |, g( P  n[*]将签名后的Transaction发送到P2P网络中。% v! t8 }+ D6 F# k7 H, d: m
1 S& f  I5 s) B4 S' l' H! v
构建原始交易RawTransaction
' @% A4 T) e( r) [9 c现在假设我有一个地址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。
3 _2 g- H$ [5 ~2 |以下是我用Go基于btcd写的示例代码,这里我们就构建好了一个RawTransaction。
1 L" ~. r* G6 q8 ~func buildRawTx() *wire.MsgTx {2 \- G9 h- E1 o% h2 T
//https://testnet.blockchain.info/tx/f0d9d482eb122535e32a3ae92809dd87839e63410d5fd52816fc9fc6215018cc?show_adv=true
9 w) h  g% H, A% a% d6 r/ A5 ctx := wire.NewMsgTx(wire.TxVersion)
6 x- c' f9 D7 X# _/ b- O6 J. Q//https://testnet.blockchain.info/tx-index/239152566/1 0.4BTC
( L* K+ k+ u6 jutxoHash, _ := chainhash.NewHashFromStr("1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52")5 h- g9 ]8 @/ ^4 ?3 r4 I" U9 b
point := wire.OutPoint{Hash: *utxoHash, Index: 1}& i& w2 ?7 j8 |/ O7 m; Y/ |
//构建第一个Input,指向一个0.4BTC的UTXO,第二个参数是解锁脚本,现在是nil2 N1 J* V3 {+ I$ @  Z8 k, }( n$ G
    tx.AddTxIn(wire.NewTxIn(&point, nil, nil))
4 y, h! l* G1 L. T, F0 Z4 J//https://testnet.blockchain.info/tx-index/239157459/1 1.1BTC
3 P8 J0 n2 Z1 H7 F' p1 F, u' EutxoHash2, _ := chainhash.NewHashFromStr("24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66")
  Z( ^  L9 w! @; T# F; J' @point2 := wire.OutPoint{Hash: *utxoHash2, Index: 1}7 u: l# O% K; R$ b% V0 j4 t
//构建第二个Input,指向一个1.1BTC的UTXO,第二个参数是解锁脚本,现在是nil
" b9 o- o5 W/ ^' u8 m, K    tx.AddTxIn(wire.NewTxIn(&point2, nil, nil))) O- j+ F5 ?6 D5 ^( {0 Y( g# O4 P
//找零的地址(这里是16进制形式,变成Base58格式就是mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV)$ N. u! x  g$ T' \- V9 n" a4 c
pubKeyHash, _ := hex.DecodeString("b5407cec767317d41442aab35bad2712626e17ca")
; h" B3 m2 V5 H' M/ Zlock, _ := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160)./ C9 e; Y2 ^! j* F$ b7 d/ D3 Y7 N
AddData(pubKeyHash).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).
9 I0 E( G$ z8 rScript()
. r3 {7 C7 I' h. c5 F; f//构建第一个Output,是找零0.2991024 BTC
7 |$ \7 w* |" L) B1 ^, o; `    tx.AddTxOut(wire.NewTxOut(29910240, lock))
& D' |3 N* J1 z9 f; J+ W//支付给了某个地址,仍然是16进制形式,Base58形式是:mxqnGTekzKqnMqNFHKYi8FhV99WcvQGhfH。& a/ c/ [$ |! I8 ?6 d5 l( t
pubKeyHash2, _ := hex.DecodeString("be09abcbfda1f2c26899f062979ab0708731235a")+ t/ ~7 S, E0 b
lock2, _ := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160).8 n2 p6 ?2 o* C) [/ f' [: o2 i0 J
AddData(pubKeyHash2).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).
6 u8 {, e0 T5 u# ^Script()
' [4 Q! Y  o; @/ ?3 v//构建第二个Output,支付1.2 BTC出去2 d% R7 t$ I3 Y( h3 e! l
    tx.AddTxOut(wire.NewTxOut(120000000, lock2))
" Y# e( }& @* F* o' Yreturn tx
( @9 y' z' ^7 l8 W}
. g% G, a9 i/ {6 g+ D$ j' _7 \交易的签名过程7 c" `* {# ~6 Y$ d8 K
现在我们知道私钥,需要对该交易进行签名,因为有2个Input,所以我们要签名2次,每个签名的原理是一样的,我就以第一个Input为例来说明吧。6 K- o1 n" b& j+ X3 c
在比特币中,对一笔交易的签名流程是这样的:/ C/ I( X6 B* \, c; Q  V
1.查找该笔交易对应的UTXO7 V1 i! T9 o: j3 d
2.获得该UTXO对应的锁定脚本6 ]; L: A/ I" y; s9 ^& q
3.复制该交易对象,并在复制副本中将该Input的解锁脚本字段的值设置为对应的锁定脚本
0 d* [- G- c" Y+ E! H0 H, ?" O* r4.清除其他Input的解锁脚本字段4 z$ W1 K2 c) G# B
5.对这个改造后的交易对象计算Hash
6 `( k+ K3 e9 L- A% G. k8 {6.使用私钥对Hash进行签名。' Q% c0 `' V- o( t1 l6 U! ^
用表格的形式可以更容易表达:
5 G* S$ \# S% t! y* o, m& w4 g# Q这是原始未签名的交易RawTransaction,主要是第二列和第三列:
2 N: O: ?1 y/ l; v  o
1 D8 ^( ~: Z( R% M% b6 HUTXO        Input        Output
, ^* m# \- _; }6 E8 cTxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,
# P/ L- g/ A8 w  ~OutIndex:1,
3 A* }0 H; P4 c# g7 f* i) _Amount:0.4BTC,PkScript:
6 Y; [6 K6 @# i3 j- N3 ?/ qOP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG. m0 @- C* ^/ x. q4 [, J5 h  V
PreviousOutPoint={
2 U2 V4 t0 J5 ~# ?1 CTxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,% ]& z6 l7 i$ F5 z( _1 p# s
OutIndex:1}
3 Z; W; G4 L! P" ?SignatureScript =NULL,Sequence =0xFFFFFFFF
7 @+ e  i! v9 }' f  e! z4 CValue=29910240# B& c: C- \5 o% Y2 A
PkScript=
( Y4 ^! i/ q# }! xOP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG) d4 T; c% K3 j6 V) \7 ?
TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,& V1 K' M1 A0 b3 N$ Z  K. ~
OutIndex:1,* |3 t0 |% j# |/ Q8 y: t. w
Amount:1.1BTC,PkScript:4 r5 i4 t$ f) P+ Y% T6 W
OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG
6 e" M! b5 g' E( t8 KPreviousOutPoint={
% v( t7 T7 U% u2 ~3 {5 RTxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,' E1 ^( h8 U# t2 y+ i. G, u
OutIndex:1}
( A0 V% I0 b9 U. ?9 a, ESignatureScript =NULL,Sequence =0xFFFFFFFF6 o: `: P) ]% e) Q
Value=120000000
1 k! f3 G" A& H9 A" r) d1 cPkScript=* f# {4 m; c& A4 j6 w( @5 [; S
OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG4 y3 ^( M5 q0 Z9 `5 ?7 x
接下来我们要对第一个Input签名,于是我们需要将交易复制一个副本,并改为:
! z/ `$ x+ ~. }. c& l' uInput        Output
7 h; ]$ U+ t$ F/ \PreviousOutPoint={# S/ N& C: C0 t6 `9 ~4 D% D) I  T
TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,
: o% i: x% F6 [$ w- `5 jOutIndex:1}
6 N( O4 G( n1 C( u& r+ t( G( pSignatureScript =( q" Y, c/ {4 z* I; N4 j' n
OP_DUP OP_HASH160 PUSHDATA(20) b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG+ O3 i5 E" ]$ m  A/ j: m5 e; X& u
,Sequence =0xFFFFFFFF- X- _4 a& I" C4 t; |
Value=29910240
6 Z: F8 `- Q, r% N1 c) R, X+ \PkScript=
& E' }9 _- U; d+ s0 |0 ZOP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG0 U) I3 O' ^" ~( w
PreviousOutPoint={
) R9 P( }9 Z' `0 WTxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,
6 y# w6 l- o9 l9 ~" UOutIndex:1}
4 A2 X; i! L  y( z3 @/ K9 t1 j+ pSignatureScript =NULL,Sequence =0xFFFFFFFF) g0 `, F8 ?& ?/ N( `' n/ X5 m0 V/ g
Value=120000000; V. p1 `) Y* R2 s
PkScript=7 ]9 I! m5 [" X0 u! z
OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG
) M: E( e- R7 N/ @2 U! b接下来对这个交易计算Hash,然后进行签名。得到签名结果:3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd22ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501 而我们知道这个地址的公钥是:038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb
( y/ \6 r' X" e5 X6 z5 y1 g所以签完名后,我们的交易变成:
/ Q5 `. A4 b+ `* jInput        Output# z0 q1 o  Z5 G8 e' b8 `. O$ g+ S
PreviousOutPoint={
* F2 H, {9 V6 p9 c' FTxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,
3 a0 D& p' X4 v" ]  r0 \% qOutIndex:1}- V! K. E, {6 n8 ?, ?4 H
SignatureScript =
9 n; u% x  u4 x8 t, S9 M8 OPUSHDATA(72)[3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd2
: F' c. J3 w/ o) R7 {1 \, ~9 v4 [2ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]
9 c8 y5 c! u3 p+ v% K* ^5 C4 G,Sequence =0xFFFFFFFF
+ d3 M" y) r0 c, j+ H& vValue=29910240* F) @4 M+ Y4 I% ^9 o& y0 K
PkScript=- c! t5 {8 I% s2 x2 O7 O
OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG
! `! g' C* |' g0 g. `$ rPreviousOutPoint={1 k2 s: F; M4 L( ?3 x8 A7 V
TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,9 I# `' V: Z0 d( M: s
OutIndex:1}+ b& _: s( d, V  i: T
SignatureScript =NULL,Sequence =0xFFFFFFFF
& b2 I; T, T& S( n/ J# s, p$ oValue=120000000% |1 t, y# L% z! q. w9 x
PkScript=) g; p" `+ {$ L
OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG
* f" F' V. h8 }, a/ g2 K这才只是完成了第一个Input的签名,接下来我们再对第二个Input进行签名,同样的道理,我们需要制造一个交易的副本,然后把第一个Input的SignatureScript清空,然后给第二个Input的SignatureScript赋值:( w1 C+ T! c$ ]8 B0 _  _
Input        Output
: N# J) S9 W9 l& C( T3 VPreviousOutPoint={
; L1 [" u/ p! e2 ]1 u& yTxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,7 a" k; X3 h* k4 f6 I
OutIndex:1}
' a% v8 g; Z% `; e( k) M! V% {SignatureScript =NULL,Sequence =0xFFFFFFFF. ^1 Z7 B9 r1 k6 u, |
Value=29910240
. Z! n* |2 c. X% t! l8 zPkScript=
2 ]; e8 J$ k! L" y, TOP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG
* c6 H8 [5 X+ A$ h2 uPreviousOutPoint={; W  u3 o. U0 c* w/ A6 f8 t
TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,
) b$ g0 Q" @' r" W" ~" W8 fOutIndex:1}( R5 f& E/ a& z: ]  o
SignatureScript =OP_DUP OP_HASH160 PUSHDATA(20) b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG
( X# w6 c3 ]4 `! O8 a,Sequence =0xFFFFFFFF3 z2 [& l. M. e% h0 i+ ^
Value=120000000
4 Y* v9 _, y* a7 v; HPkScript=0 x" v# w! Q& H; Q
OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG3 X9 S& H3 R* t
显然这个副本与第一个签名时的数据是不一样的,所以签名结果也不一样,最终签名结果为:30440220196bce75f0a25ac8afa7218aefc86cba3924845450f3d311c89e9c2a3438a99c0220230bed598a610be971ca49690f4b42ac2acfa80c09d4cbabd278b03c824af14501,当然我们因为是同一个地址,所以公钥是一样的:038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb* b8 G1 R0 z+ k7 W( S' \  F/ K! O
我们把这个签名和公钥再放回原始交易中,就变成我们需要的完整签名的交易:; s1 V, I, F7 g
Input        Output( u1 N1 m& @( x+ C5 I
PreviousOutPoint={6 H2 R) o& ^/ O% ?# N/ ~$ X
TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,5 Q* u% d6 p) ^5 c& S! T! [
OutIndex:1}
9 g" M2 J' I+ T, O9 ]SignatureScript =PUSHDATA(72)[3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd22ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]
7 Y; T6 F  ?9 o+ t,Sequence =0xFFFFFFFF
" ~1 g' b8 M# H* y# R- K* `8 FValue=29910240+ ?" N0 `; W+ x- U. G
PkScript=6 _! d8 u* B2 p& r: B
OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG
8 ]; p; I2 d, D8 HPreviousOutPoint={! s- X7 a% Y# J& W) h2 k
TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,) V3 a, y3 R6 J/ M- H# q. d* I+ P
OutIndex:1}8 Q: R) v6 F, k8 z7 F' [' f2 l8 B& l
SignatureScript =PUSHDATA(71)[30440220196bce75f0a25ac8afa7218aefc86cba3924845450f3d311c89e9c2a3438a99c0220230bed598a610be971ca49690f4b42ac2acfa80c09d4cbabd278b03c824af14501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]* ^: }+ c, d& y5 f
,Sequence =0xFFFFFFFF
# ^: ]0 n' }% `1 N% q* C, J' W这就是一个真实完整的交易了,接下来就可以通过P2P网络发送该交易,并最终被矿工打包确认。
* O$ D6 V/ B* d, P7 b& ~( U总结; o$ b" x( M; w- P/ V
实际上在比特币的源码中比我上面说的还要复杂一些,还涉及到这个hash是对整个交易进行SigHashAll还是SigHashSingle或者SigHashNone,这些都是很特殊的情况,一般的比特币钱包也不支持,具体可以参加精通比特币书中的介绍:6.5.3签名哈希类型( SIGHASH)
' i- N5 n. v8 X& C普通来说,我们要对一笔交易进行签名或者验签,就是把当前Input中的解锁脚本替换成锁定脚本,而其他Input的解锁脚本情况,然后计算Hash和签名!& f' e% n. [% K2 u5 _& K
其实我还是有点不明白,为什么比特币中不直接对没有任何解锁脚本的RawTransaction进行签名呢?而是非要加上锁定脚本来签名?不知道这里面有什么更深的考虑。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

V刘晨曦 初中生
  • 粉丝

    0

  • 关注

    3

  • 主题

    14