Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
180
0
0
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。. c3 K, M9 D' f% b) g0 }) ^
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链 x5 Z4 @: n3 W! F$ G$ N
注意事项:
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L255
1.创建私钥和公钥. b' M0 `0 h: R4 h$ {
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) {) N7 Q* E# m3 ~6 U1 Q% y) z
xprv, err = NewXPrv(r)
if err != nil {
return
}
return xprv, xprv.XPub(), nil% M* ~* O+ c7 m( \0 I
}# t" k) b+ |8 f
2.根据公钥创建接收对象
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {' z2 t8 O8 q3 v6 W( s7 u
pubKey := xpub.PublicKey()
pubHash := crypto.Ripemd160(pubKey)
// TODO: pass different params due to config
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)+ x& p+ o. B( ]/ R9 j* f
if err != nil {* c1 I+ V; `0 V9 O% s
return nil, err
}
control, err := vmutil.P2WPKHProgram([]byte(pubHash))+ K2 d# k8 y+ h
if err != nil {
return nil, err
}8 \ Z L' G2 n o
return &CtrlProgram{
Address: address.EncodeAddress(),6 |- O9 p0 D4 \! q) m3 @% f. G9 t/ X
ControlProgram: control,3 D, s/ Y4 A4 |* ]1 |
}, nil
}
创建多签地址参考代码account/accounts.go#L294进行相应改造为:2 ]' P$ q+ i4 U. b( w. z+ h
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {" _5 Q. N4 Y0 ^5 t2 W1 a
derivedPKs := chainkd.XPubKeys(xpubs)
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))% A: V, r9 V* q0 f+ f( Y+ L# z" I3 n
if err != nil {: T) V/ Q C8 E! @
return nil, err0 ~& g, a! d3 h9 v& p
}
scriptHash := crypto.Sha256(signScript)
// TODO: pass different params due to config
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
if err != nil {* r( [; }! X, L5 e, }0 ]
return nil, err& h8 V- B+ R2 ]& v; L8 l7 ?
}
control, err := vmutil.P2WSHProgram(scriptHash)2 {" @6 I( `( O
if err != nil {
return nil, err1 b1 g X: |1 Z/ m% E7 `. P Q
}2 X6 L' f: L, ]. c; ]
return &CtrlProgram{$ n1 b2 k3 U/ X! }5 Y; _4 {- C
Address: address.EncodeAddress(),
ControlProgram: control,7 F$ F" l4 f: e. G( v$ J) P
}, nil, t: l5 m8 z" l. {( T+ ^8 R. W
}
3.找到可花费的utxo
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)5 C5 D7 N4 N! \7 i7 ^) [3 r; G+ J$ O
// UTXO describes an individual account utxo.1 w/ h* A& d O( X% A$ X
type UTXO struct {8 c( j x6 }, o
OutputID bc.Hash
SourceID bc.Hash7 _6 m9 C4 z" e, g7 l
// Avoiding AssetAmount here so that new(utxo) doesn't produce an
// AssetAmount with a nil AssetId.
AssetID bc.AssetID/ z9 W: D( ~. \4 G; y
Amount uint64- Q5 {) W( }! a; B9 y
SourcePos uint64
ControlProgram []byte& ~& B7 ~. D3 x: \" I3 V: T
AccountID string
Address string
ControlProgramIndex uint64
ValidHeight uint64
Change bool
}* p# Q, p9 m! d* I2 Y9 L/ z f
涉及utxo构造交易的相关字段说明如下:
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址; @' O8 ~- k1 n# f+ ^& E
2 k9 t) J+ R% ~
上述这些utxo的字段信息可以从get-block接口返回结果的transaction中找到,其相关的结构体如下:(参考代码api/block_retrieve.go#L26)4 I! S% a6 B) t9 m5 B a1 C
// BlockTx is the tx struct for getBlock func
type BlockTx struct {' ~! e _ v3 o! `2 g# o
ID bc.Hash `json:"id"`9 l2 ~, p [. a
Version uint64 `json:"version"`9 I+ C1 i! U' u( |; P0 e
Size uint64 `json:"size"`
TimeRange uint64 `json:"time_range"`
Inputs []*query.AnnotatedInput `json:"inputs"`
Outputs []*query.AnnotatedOutput `json:"outputs"`9 Y! X. l# W- `3 m
StatusFail bool `json:"status_fail"`
MuxID bc.Hash `json:"mux_id"`
}
//AnnotatedOutput means an annotated transaction output.# n! J+ @/ l# d0 o" U+ K4 W
type AnnotatedOutput struct {
Type string `json:"type"`# o5 M. }; k0 i; }$ O
OutputID bc.Hash `json:"id"`: C+ {& a0 E* e( p2 E- V$ |
TransactionID *bc.Hash `json:"transaction_id,omitempty"`
Position int `json:"position"`
AssetID bc.AssetID `json:"asset_id"`; P6 a' U1 ^5 M4 `5 s: O# S
AssetAlias string `json:"asset_alias,omitempty"`
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`% l- n# [" O% m( }1 B/ C" h" C
Amount uint64 `json:"amount"`' y# O# B3 S8 V' \! F: Y+ P
AccountID string `json:"account_id,omitempty"`
AccountAlias string `json:"account_alias,omitempty"`
ControlProgram chainjson.HexBytes `json:"control_program"`2 j' s- |+ r5 \# W& Y, W; e
Address string `json:"address,omitempty"`" i* Z3 _* s+ p9 I
}: g8 |! f8 z. k; {1 ?
utxo跟get-block返回结果的字段对应关系如下:
`SourceID` - `json:"mux_id"`- k- M; q5 Q. h. L! {) H
`AssetID` - `json:"asset_id"`
`Amount` - `json:"amount"`
`SourcePos` - `json:"position"`
`ControlProgram` - `json:"control_program"`, n( Y$ A! t0 ^+ @4 o/ Y( y" c
`Address` - `json:"address,omitempty"`
4.通过utxo构造交易
通过utxo构造交易就是使用spend_account_unspent_output的方式来花费指定的utxo。/ q, U2 c) \+ i+ E) d
第一步,通过utxo构造交易输入TxInput和签名需要的数据信息SigningInstruction,该部分功能可以参考代码account/builder.go#L169进行相应改造为:# | w5 z4 c& X, {3 c% y. m# L2 A
// UtxoToInputs convert an utxo to the txinput+ [# o; C1 a0 E: ]( t/ d. ?, K
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): s4 x4 f; e) E! D" B4 |0 u" {
sigInst := &txbuilder.SigningInstruction{}1 e {$ {5 t3 u6 G+ ?, B; c' j
if u.Address == "" {
return txInput, sigInst, nil* \/ D# I# s. {
}9 B7 f3 v# o; N) P
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)1 x- V) h' ?$ N" \' {% m S
if err != nil {1 I; v; b& N: d X9 O- Z& a/ v
return nil, nil, err
}7 v0 F, e! L" k/ v5 m4 o
switch address.(type) {
case *common.AddressWitnessPubKeyHash:
derivedPK := xpubs[0].PublicKey()
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))& B; C |' g6 L- q6 ]5 \+ Y/ m
case *common.AddressWitnessScriptHash:3 {# v1 }/ }7 G) ^7 x
derivedPKs := chainkd.XPubKeys(xpubs)$ q7 M; y+ I& ?5 |
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil { A/ X* L g" s- @' Y7 X7 [( l, \6 ~
return nil, nil, err
}
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))2 X1 m7 j0 T6 |& j1 L9 z3 @1 [
default:
return nil, nil, errors.New("unsupport address type")5 \; S) y8 ]! y( ], f( @2 [
}7 x' K/ a8 Z! w6 K
return txInput, sigInst, nil
}" J0 j3 [' M* R
第二步,通过utxo构造交易输出TxOutput
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output struct
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {+ _( a. r. ? v
return &TxOutput{
AssetVersion: 1, M& X& z. S0 G' ?. _9 q! k. \! I
OutputCommitment: OutputCommitment{, _* k4 g7 {' [3 I: z/ i& F6 u
AssetAmount: bc.AssetAmount{
AssetId: &assetID,
Amount: amount,
},7 Y" f" m" S/ L$ m
VMVersion: 1,
ControlProgram: controlProgram,( A9 @3 i" K. O. r0 {/ [
},4 G" Q8 b; l, y
}9 {# S" x' s- d
}
5.组合交易的input和output构成交易模板
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:
type InputAndSigInst struct {
input *types.TxInput
sigInst *SigningInstruction
}1 A$ t* V' }! B, p
// Build build transactions with template
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {- W( A9 C# A& {1 O9 v1 F9 d
tpl := &Template{}
tx := &types.TxData{}
// Add all the built outputs.* F9 F& x+ p7 _2 S
tx.Outputs = append(tx.Outputs, outputs...)
// Add all the built inputs and their corresponding signing instructions.
for _, in := range inputs {3 A" N; K* G* ^) O) ?* C, R
// Empty signature arrays should be serialized as empty arrays, not null.: g- ^$ f! Y9 L# a
in.sigInst.Position = uint32(len(inputs))
if in.sigInst.WitnessComponents == nil {
in.sigInst.WitnessComponents = []witnessComponent{}
}+ `* J; S( K9 j+ N3 E
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)
tx.Inputs = append(tx.Inputs, in.input)8 ^. ~ s2 K( h7 t: @1 j. }
}1 d! |! n( R3 ~3 C2 I. {/ F8 z
tpl.Transaction = types.NewTx(*tx)( k. c( l u: Y8 o- i' Q% F
return tpl, tx, nil
}0 x" ]4 {" {5 s# L# N# J' c
6.对构造的交易进行签名
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)
// Sign will try to sign all the witness) A" \3 ]$ Q- @$ J- w! j
func Sign(tpl *Template, xprv chainkd.XPrv) error {; g6 f9 m; G+ Y* F( E- x
for i, sigInst := range tpl.SigningInstructions {
h := tpl.Hash(uint32(i)).Byte32()1 D. I \3 ?: U2 r; h: x2 x) o7 X
sig := xprv.Sign(h[:])
rawTxSig := &RawTxSigWitness{- }# W4 F" \' ^
Quorum: 1,
Sigs: []json.HexBytes{sig},
}
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)
}* [/ m& n( o$ h4 z: a- { l
return materializeWitnesses(tpl)! Q8 l* w, W: Q( q8 @/ U) Y3 b. G
}+ W8 _( ]* {6 a, S6 p
7.提交交易上链8 V5 T0 [" I, A7 e7 m* J
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可0 U+ I k+ P; M0 o
/ ?$ H. C# D9 j0 j& i: _! O; n# H7 i
成为第一个吐槽的人