Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
124
0
0
Github地址:https://github.com/Bytom/bytom( W4 { A H! r$ g7 X" l: L0 |; D7 t! i
Gitee地址:https://gitee.com/BytomBlockchain/bytom- E) J, k; r1 E3 I
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。3 E8 q; b# ]5 m8 m7 `& n0 p
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链
注意事项:) K; c$ C' o) s ~0 c/ G! k
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L2558 E( C5 _$ {6 m
1.创建私钥和公钥
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) {
xprv, err = NewXPrv(r)* }2 K" z( @8 o
if err != nil {
return% s% p. \" a" x% m" H
}9 F, [6 _) u. p8 ~
return xprv, xprv.XPub(), nil" H7 Q" Z- b( ^# U& B* z
}7 q; v9 @3 G: s' w
2.根据公钥创建接收对象
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:: I+ V6 }; u% y- u
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {) [- D: R4 ~8 a$ T0 j: q+ C
pubKey := xpub.PublicKey(): k a$ X1 Y, F3 o5 ?
pubHash := crypto.Ripemd160(pubKey)3 U, a$ s+ W4 @2 K: [6 f% J
// TODO: pass different params due to config
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
if err != nil {
return nil, err( v% g& X" m( H
}
control, err := vmutil.P2WPKHProgram([]byte(pubHash))+ G9 y0 e: g: x5 ?: a6 d+ _- ?' v
if err != nil {
return nil, err
}
return &CtrlProgram{
Address: address.EncodeAddress(),
ControlProgram: control,2 y( R1 s8 o E
}, nil. z3 D$ N9 w* a4 c+ q
}
创建多签地址参考代码account/accounts.go#L294进行相应改造为:
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {+ y# \& v% N2 c6 l7 ?; V
derivedPKs := chainkd.XPubKeys(xpubs); {" v0 Q& W7 @( g
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {9 D+ e5 ]0 C4 k- q# b& e: C
return nil, err
}- C, ?7 o/ V4 D. d) I' g9 Q- x$ X
scriptHash := crypto.Sha256(signScript)
// TODO: pass different params due to config: f( d' e( p8 \ T0 R( A( u z
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams), ~% x0 ^8 D) r( k, n- Y
if err != nil {
return nil, err
}/ c7 h z3 X( @" e/ o; P. g
control, err := vmutil.P2WSHProgram(scriptHash), b: E( d" x8 p3 C! p7 ]
if err != nil {4 S4 v7 w1 U6 V7 U- r
return nil, err
}. A+ b- c6 e7 Y4 P: B5 |
return &CtrlProgram{
Address: address.EncodeAddress(),
ControlProgram: control,7 Z" F. v! l2 K& E5 n7 c
}, nil! q! A5 W' s! J& H
}
3.找到可花费的utxo J$ L8 F) h9 u |
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)
// UTXO describes an individual account utxo.7 t% e) r6 M) ?0 |+ V' N' I
type UTXO struct {
OutputID bc.Hash8 G* O5 g9 f- O) N. j
SourceID bc.Hash
// Avoiding AssetAmount here so that new(utxo) doesn't produce an# j) \8 j; f/ ]( t! k# B8 E! z" z
// AssetAmount with a nil AssetId.+ y- q) p8 {7 [% \, ^7 [
AssetID bc.AssetID' P+ y0 ~4 |% \+ e7 l
Amount uint64
SourcePos uint642 R! m/ W6 V* b
ControlProgram []byte) d R; b" k7 B4 G4 h" H
AccountID string
Address string
ControlProgramIndex uint64
ValidHeight uint64
Change bool4 F- c+ L J& F; Z9 h
}
涉及utxo构造交易的相关字段说明如下:4 i( f7 @0 S: [' Q
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址0 y% T, b! J: |& f: c
上述这些utxo的字段信息可以从get-block接口返回结果的transaction中找到,其相关的结构体如下:(参考代码api/block_retrieve.go#L26)
// BlockTx is the tx struct for getBlock func
type BlockTx struct {
ID bc.Hash `json:"id"`
Version uint64 `json:"version"`& {3 {! T5 U7 T
Size uint64 `json:"size"`- H2 \( F- D9 P g2 }
TimeRange uint64 `json:"time_range"`' [& o0 e8 u. q M2 S- R
Inputs []*query.AnnotatedInput `json:"inputs"`' j9 ?7 R. G, I
Outputs []*query.AnnotatedOutput `json:"outputs"`
StatusFail bool `json:"status_fail"`: h- B5 C0 w6 a
MuxID bc.Hash `json:"mux_id"`8 g5 I1 ?3 s. P/ A# M
}
//AnnotatedOutput means an annotated transaction output./ H7 z# U# Y8 N, E
type AnnotatedOutput struct {& g* B3 w$ Y. ]3 o
Type string `json:"type"` J& e& T9 i+ ]$ @
OutputID bc.Hash `json:"id"`' l0 R* Y% {9 L W
TransactionID *bc.Hash `json:"transaction_id,omitempty"`! ]6 {* \7 e3 r- A+ e
Position int `json:"position"`) N" `/ o) B6 h ?: B6 v, E7 Y% q
AssetID bc.AssetID `json:"asset_id"`2 F9 |4 U. b6 v% ^- p4 k1 r3 c& c
AssetAlias string `json:"asset_alias,omitempty"`( ~* b/ I7 C1 @3 G. R3 p) Y' ?& n% o
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`5 ~2 }8 Z' P1 c) L( U3 `* ^8 _
Amount uint64 `json:"amount"`1 n8 i8 ^9 ?" U4 H9 g- i" L; p/ Y
AccountID string `json:"account_id,omitempty"`% w9 Z, i, Q4 Y
AccountAlias string `json:"account_alias,omitempty"`
ControlProgram chainjson.HexBytes `json:"control_program"`9 O( h. J, `9 p9 L
Address string `json:"address,omitempty"`$ v U. L# [- b# Q# P7 z
}
utxo跟get-block返回结果的字段对应关系如下:
`SourceID` - `json:"mux_id"`
`AssetID` - `json:"asset_id"`
`Amount` - `json:"amount"`
`SourcePos` - `json:"position"`
`ControlProgram` - `json:"control_program"`
`Address` - `json:"address,omitempty"`
4.通过utxo构造交易
通过utxo构造交易就是使用spend_account_unspent_output的方式来花费指定的utxo。
第一步,通过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) {& m! {9 ^0 d% R& [1 k2 S
txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
sigInst := &txbuilder.SigningInstruction{}. F3 [& c5 G" B
if u.Address == "" {
return txInput, sigInst, nil- ~$ R' p4 H8 ?* C8 Q. Y% D# q: \9 R
}( L4 ?- p* ^/ Z0 V' G
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
if err != nil {
return nil, nil, err! _7 @. N, R1 Y: ]* y' j
}% G2 Y6 D) Z' v ?9 B+ B" `1 o! W
switch address.(type) {
case *common.AddressWitnessPubKeyHash:5 p/ w4 O- V4 ~: {" Q& m& h
derivedPK := xpubs[0].PublicKey()
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
case *common.AddressWitnessScriptHash:' Q( x2 B' q9 t+ d+ D) ^
derivedPKs := chainkd.XPubKeys(xpubs)
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))4 x9 s$ S" \& ?9 E/ P; E& `: v0 ?
if err != nil {8 J) J% Y# P1 ]8 G9 ]
return nil, nil, err
}- G( A- ?$ x8 Y. d
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
default:+ p4 o7 W) z3 h0 d( j8 Q! [5 `5 V6 d
return nil, nil, errors.New("unsupport address type")
}
return txInput, sigInst, nil, \1 H( n9 ?% `5 b
}) \) }* j# f- |0 m6 D5 c4 E
第二步,通过utxo构造交易输出TxOutput
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output struct, r) y, T, Q; f' u
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {
return &TxOutput{
AssetVersion: 1,$ p" ^: i5 Y; ~/ }) G+ t4 x
OutputCommitment: OutputCommitment{* E, T& t; O6 l
AssetAmount: bc.AssetAmount{
AssetId: &assetID,
Amount: amount,
},4 l1 _' N. K; S" a! z9 l
VMVersion: 1,: m+ n- u" P2 H# d( N9 V
ControlProgram: controlProgram,
},
}! S( ?$ b: A9 l! [. x3 g+ ^1 t5 m
}* _( Q7 ^2 h1 W
5.组合交易的input和output构成交易模板6 B* v+ b2 x; S
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:- B: f( A1 p' d6 E b5 J
type InputAndSigInst struct {! J& x6 z3 Q6 w2 x& z
input *types.TxInput0 M# O$ w( x: r1 N' `5 t9 P4 Y
sigInst *SigningInstruction
}
// Build build transactions with template9 ^0 l6 i% X& {! ?$ c& u
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {; i) R4 A* i* T: ?; t8 ]
tpl := &Template{}
tx := &types.TxData{}+ L5 H! ?% r* o& g( D
// Add all the built outputs.7 w; G5 c5 N2 a% C% ^4 w% x
tx.Outputs = append(tx.Outputs, outputs...)
// Add all the built inputs and their corresponding signing instructions.
for _, in := range inputs {
// Empty signature arrays should be serialized as empty arrays, not null.
in.sigInst.Position = uint32(len(inputs))
if in.sigInst.WitnessComponents == nil {" O! w6 v: E# J, W4 l+ c, V2 i
in.sigInst.WitnessComponents = []witnessComponent{}: t' _# w# o+ W+ I7 S
}
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)! t" V4 r' g) O9 N" K
tx.Inputs = append(tx.Inputs, in.input)
}
tpl.Transaction = types.NewTx(*tx)
return tpl, tx, nil8 X' M A2 K' {
}& _ f, R2 c6 q y1 S1 q5 P! r0 J
6.对构造的交易进行签名
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)7 p. a, i. E, d7 Z3 z3 h
// Sign will try to sign all the witness
func Sign(tpl *Template, xprv chainkd.XPrv) error {/ _$ d1 f" m1 C3 ?* B
for i, sigInst := range tpl.SigningInstructions {
h := tpl.Hash(uint32(i)).Byte32()
sig := xprv.Sign(h[:])9 V, c+ g: d& g1 [+ H
rawTxSig := &RawTxSigWitness{7 K# x k- z, p e- A
Quorum: 1,9 X* ^6 N6 g$ m4 m8 _+ _6 h
Sigs: []json.HexBytes{sig},9 b! S. Z1 n& ^
}' W! ]% D e: ]+ h
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)
}
return materializeWitnesses(tpl)2 _: V4 d5 B3 w6 H
}
7.提交交易上链' r; }3 c7 J2 `, j; X
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可
成为第一个吐槽的人