Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
173
0
0
Github地址:https://github.com/Bytom/bytom( R$ ?' A- g! E
Gitee地址:https://gitee.com/BytomBlockchain/bytom# H' H. q% L% q ?3 l4 J" _
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。) u. z6 k( T1 O: D% Q2 B& b
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链
+ A4 g- y! N+ {& f' H% f
注意事项:+ f% t) [' E! q* Q0 z) r: I: O$ Z
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L255$ J) B* d# S% u" p, m
1.创建私钥和公钥# Q2 E. R$ M9 q3 a: ]. d% }
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) {
xprv, err = NewXPrv(r)& i' p/ x2 l- U! k+ T0 D
if err != nil {
return
} U# ]' c# X# T# ]1 e$ c& B" D
return xprv, xprv.XPub(), nil' V$ Z' q+ ]3 [$ w6 q4 { b) Y
}
2.根据公钥创建接收对象5 m$ o Z9 P6 [8 \+ O
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {
pubKey := xpub.PublicKey()
pubHash := crypto.Ripemd160(pubKey)$ z, v. w+ @: _8 R
// TODO: pass different params due to config% M" ]& D3 y" @, ]
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
if err != nil {
return nil, err
}; W$ s! p+ b- D0 Q
control, err := vmutil.P2WPKHProgram([]byte(pubHash))
if err != nil {1 |7 ]! J0 k0 e* R% X3 l, y
return nil, err$ P# c3 |# H% `, U1 ^9 g* M
}
return &CtrlProgram{
Address: address.EncodeAddress(),$ Q1 V1 K5 T3 W9 z# w2 [7 ^ G. c; x
ControlProgram: control,
}, nil/ S; g4 R1 M- n- ^- l2 w
}
创建多签地址参考代码account/accounts.go#L294进行相应改造为:
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {; ?+ Z: x3 w q
derivedPKs := chainkd.XPubKeys(xpubs)
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {
return nil, err( O+ ?8 l8 o1 f, |
}
scriptHash := crypto.Sha256(signScript)7 ?8 p& S, b+ r5 @* O
// TODO: pass different params due to config4 W1 Z! V' P/ H+ u+ f
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
if err != nil {0 q( ~% ]0 q! }, L5 a
return nil, err2 r: U3 [! N) v! d
}$ J1 V: K: d' e& R. n# T$ f0 Q& { O
control, err := vmutil.P2WSHProgram(scriptHash)5 }8 O" f% z( u. m4 c/ k. G+ v& ^
if err != nil {4 C8 B5 n* L, E9 v' ^) M2 ^0 F6 G
return nil, err: P$ t1 U! c# h7 i
}
return &CtrlProgram{- x3 q2 L5 i! k
Address: address.EncodeAddress(),' g- t3 n- ?5 y5 u: e7 U
ControlProgram: control,( r; b, ^4 H+ k2 w
}, nil) Z) p4 b( Z( c: l. a7 Y& o
}
3.找到可花费的utxo! @0 T7 v( u" d. i( q
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)6 ?2 T1 ~1 d& G I9 H5 I- G$ e4 C
// UTXO describes an individual account utxo.
type UTXO struct {
OutputID bc.Hash, T+ B* U/ d2 P9 y8 j% ?
SourceID bc.Hash
// Avoiding AssetAmount here so that new(utxo) doesn't produce an
// AssetAmount with a nil AssetId.
AssetID bc.AssetID
Amount uint64+ ]/ H5 d K; i" l
SourcePos uint64( q4 ?& b5 g: p" y$ I; {
ControlProgram []byte1 Z9 w" _# ?9 s- N5 A
AccountID string
Address string- b' E8 H+ c. n( ~! h( ^
ControlProgramIndex uint64
ValidHeight uint64. {" F( n5 f4 u
Change bool
}: }# H( |1 e/ ]
涉及utxo构造交易的相关字段说明如下:
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址6 o/ j/ T0 K* _) r0 b
上述这些utxo的字段信息可以从get-block接口返回结果的transaction中找到,其相关的结构体如下:(参考代码api/block_retrieve.go#L26)! d0 D, m6 ~- _1 S9 f
// BlockTx is the tx struct for getBlock func
type BlockTx struct {0 d9 N1 ^) ]1 I- l' K6 [$ [: V; z3 N
ID bc.Hash `json:"id"`
Version uint64 `json:"version"`
Size uint64 `json:"size"`, a/ l& U8 Y& y. O# F" a
TimeRange uint64 `json:"time_range"` V- E/ n1 Y& C B# ]. }! c! ?$ d
Inputs []*query.AnnotatedInput `json:"inputs"`
Outputs []*query.AnnotatedOutput `json:"outputs"`
StatusFail bool `json:"status_fail"` y8 L' t- n0 M d* g
MuxID bc.Hash `json:"mux_id"`
}$ |- t$ n: m5 E$ {, b; n" E
//AnnotatedOutput means an annotated transaction output.
type AnnotatedOutput struct {
Type string `json:"type"`5 S, X. Y' u& `. x6 @+ l
OutputID bc.Hash `json:"id"`
TransactionID *bc.Hash `json:"transaction_id,omitempty"`
Position int `json:"position"`6 t& l2 Y6 O& x) ~+ g
AssetID bc.AssetID `json:"asset_id"`1 P. B o! @( q/ M0 ?9 e
AssetAlias string `json:"asset_alias,omitempty"`, G- M L" q5 x$ ~; v
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`
Amount uint64 `json:"amount"`5 e) P: W' [% B0 e' B+ z
AccountID string `json:"account_id,omitempty"`; |) w, q) c* g- Q) w8 e- A
AccountAlias string `json:"account_alias,omitempty"`
ControlProgram chainjson.HexBytes `json:"control_program"`
Address string `json:"address,omitempty"`
}4 b7 M/ z4 [* k& P7 ?
utxo跟get-block返回结果的字段对应关系如下:
`SourceID` - `json:"mux_id"`* L/ k: _2 t8 p, M: d
`AssetID` - `json:"asset_id"`: [+ k* ^, W6 s1 f1 e. @
`Amount` - `json:"amount"`& V3 } o) Z, {) f P: _
`SourcePos` - `json:"position"`% i# M" I" v `
`ControlProgram` - `json:"control_program"`
`Address` - `json:"address,omitempty"`* S; T s2 Z) m$ X/ v6 x4 l
4.通过utxo构造交易. K7 i T- b9 g" q1 Q# K! m
通过utxo构造交易就是使用spend_account_unspent_output的方式来花费指定的utxo。3 ]; x. B8 F9 Y/ _
第一步,通过utxo构造交易输入TxInput和签名需要的数据信息SigningInstruction,该部分功能可以参考代码account/builder.go#L169进行相应改造为:
// UtxoToInputs convert an utxo to the txinput
func UtxoToInputs(xpubs []chainkd.XPub, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
sigInst := &txbuilder.SigningInstruction{}6 c* V, A# W3 e( N
if u.Address == "" {
return txInput, sigInst, nil
}! ]/ `0 t M8 H7 A v8 E4 X# p+ N
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)1 z! Z+ U9 H7 x4 i3 G7 y" c2 T
if err != nil { V! {/ b' E* O3 ~5 A2 j0 T3 S* c
return nil, nil, err9 N' W" m" |1 z% K- {
}! c* T7 o) ^! P/ Y2 a5 y, b
switch address.(type) {; Y3 B4 y: }+ l( N
case *common.AddressWitnessPubKeyHash:# [. U; p5 _% f
derivedPK := xpubs[0].PublicKey()2 |& B, S# G+ I( Q L9 l( V9 P
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))! a7 O2 n" O6 d2 H2 a
case *common.AddressWitnessScriptHash:
derivedPKs := chainkd.XPubKeys(xpubs)% R: C, {/ r4 U' r, S6 q6 D1 @
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {
return nil, nil, err
}
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
default:
return nil, nil, errors.New("unsupport address type")& O% V9 N9 D2 B) i; B, }
}; F! }$ o% {+ E. }) U
return txInput, sigInst, nil: v+ o2 t+ n. n
}
第二步,通过utxo构造交易输出TxOutput
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output struct4 R! G3 \, c# Y0 n
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {/ {% l0 f0 I( F. x! p' C7 l
return &TxOutput{7 v, z# Z1 }: o( `/ Y; W3 X$ G3 P
AssetVersion: 1,9 b: a# j4 P& k# h1 i- |
OutputCommitment: OutputCommitment{/ D. }/ m5 F' u9 s
AssetAmount: bc.AssetAmount{& ?) h% c- l4 r" A) x2 s& X: v1 G$ \8 P4 h
AssetId: &assetID,
Amount: amount,$ }) w# R, t5 D. N- T
},( B2 K7 f! f0 ^8 m \7 V7 z% v V
VMVersion: 1,/ _7 k0 q8 q! e+ ]$ L
ControlProgram: controlProgram,1 O+ m) ~% X4 f0 V5 I$ F
}, E" M2 N" F6 t, [+ s& M
}
}
5.组合交易的input和output构成交易模板" u r; _. M! Z/ z" l
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:
type InputAndSigInst struct {
input *types.TxInput6 k. c& K8 F, ]) k& n+ W
sigInst *SigningInstruction, k7 n" ~( E7 B7 C
} c" h( y/ z1 r4 o/ h' P% K
// Build build transactions with template. Z$ \5 [6 w$ A9 D' M
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {1 Y. m: @: T0 W: R9 |
tpl := &Template{}
tx := &types.TxData{}
// Add all the built outputs. A/ H8 j' C; [
tx.Outputs = append(tx.Outputs, outputs...)1 X" x6 E- |$ p- |# P
// Add all the built inputs and their corresponding signing instructions./ z5 n x* Q4 M# p: S; i
for _, in := range inputs {
// Empty signature arrays should be serialized as empty arrays, not null.7 b! w5 O8 `/ C7 V
in.sigInst.Position = uint32(len(inputs))
if in.sigInst.WitnessComponents == nil {
in.sigInst.WitnessComponents = []witnessComponent{}6 ~, V% f D* P
}1 I1 G0 N9 [* H7 b( D
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)
tx.Inputs = append(tx.Inputs, in.input). [7 v6 W' G" q9 ~: ^8 g
}( H) q) Z% A5 j8 }. h9 _
tpl.Transaction = types.NewTx(*tx)
return tpl, tx, nil
}; o: P9 `. G, R/ b% ]9 v* {
6.对构造的交易进行签名) g/ n u$ O5 @/ y% w u* y
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)
// Sign will try to sign all the witness7 H( U. s3 c. G% R
func Sign(tpl *Template, xprv chainkd.XPrv) error {5 X$ N9 _7 G* F+ d" j; H, [
for i, sigInst := range tpl.SigningInstructions {2 [1 _3 ]" P1 x: r
h := tpl.Hash(uint32(i)).Byte32()
sig := xprv.Sign(h[:])9 l# e" A- c+ t9 F6 o
rawTxSig := &RawTxSigWitness{1 T+ I+ K+ T1 y, |# b! u) w
Quorum: 1,
Sigs: []json.HexBytes{sig},' V9 W8 m4 j/ x |8 r
}
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)2 `5 Y& L" d$ e4 J& Y
}
return materializeWitnesses(tpl)
}
7.提交交易上链% z3 @/ Z4 I$ A2 _% ], I# ^
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可$ x4 w( i# m3 q: j9 r( I4 C$ r
成为第一个吐槽的人