Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
110
0
0
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom l3 R3 N) s. D4 w
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链# b$ X& f3 k M0 r+ P: H* O: N
% R, I7 Y2 P( i' }
注意事项:
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L255
1.创建私钥和公钥
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥& N# Z, h6 l3 r. M( T0 z
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) { b, X! h% K2 K" \
xprv, err = NewXPrv(r), b+ [$ q( n* A- {
if err != nil {
return
}
return xprv, xprv.XPub(), nil: L. I( o$ w# F
}" u1 F; Z4 T' G7 j# Z/ N4 B
2.根据公钥创建接收对象: L' ^7 | y8 s7 }
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {2 ^( V: A3 H6 L: m1 j+ Z
pubKey := xpub.PublicKey()
pubHash := crypto.Ripemd160(pubKey)( p! W1 _$ N9 A' {+ k
// TODO: pass different params due to config5 l. T, A- _" v: v7 j
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
if err != nil {
return nil, err
}
control, err := vmutil.P2WPKHProgram([]byte(pubHash))
if err != nil {8 r% U- t9 v& d. L5 x4 B3 B! \( G6 ]
return nil, err
}
return &CtrlProgram{, |, q, C$ r# O; T: e
Address: address.EncodeAddress(),1 C+ r7 S+ y/ k8 e
ControlProgram: control,
}, nil7 Z% ]3 Y# Y. N
}
创建多签地址参考代码account/accounts.go#L294进行相应改造为:
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {7 X& L4 G+ r8 B' I2 Y1 L5 ?5 r7 d
derivedPKs := chainkd.XPubKeys(xpubs)( _" s2 c* w, v5 v- j
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {
return nil, err
}
scriptHash := crypto.Sha256(signScript)3 T1 g w& F" d3 M
// TODO: pass different params due to config+ r5 F c. B. Z: G& ^# q
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
if err != nil {! I. C% Z O0 d* s d: H5 O
return nil, err" E! X" h" A j- h
}" S, S) O+ `3 c' n" @6 _; j
control, err := vmutil.P2WSHProgram(scriptHash)
if err != nil {
return nil, err5 F+ Q. s7 Q3 n, d" Y5 u: g' q
}
return &CtrlProgram{
Address: address.EncodeAddress(),/ b! X) R# w; s) V8 y
ControlProgram: control,$ R4 l: ?4 W. ~: @8 t
}, nil8 v% k0 d9 x! g: v7 \2 {
}
3.找到可花费的utxo
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)5 _! q" J$ Z/ j
// UTXO describes an individual account utxo.2 B: `( f% R2 Y* f* g: `
type UTXO struct {- y0 a9 V, e$ V7 t7 k0 Q. R
OutputID bc.Hash
SourceID bc.Hash% U7 x/ U/ M9 p* Q7 A% j
// Avoiding AssetAmount here so that new(utxo) doesn't produce an& O0 B& B* e9 L ^
// AssetAmount with a nil AssetId.1 {/ s$ @& P* G4 h5 s. V
AssetID bc.AssetID8 d6 c0 _& P. [, p8 |
Amount uint64
SourcePos uint64
ControlProgram []byte9 ?; ?$ `/ k( K/ Y; X
AccountID string
Address string6 z# B0 k+ T# a z v5 |7 r; a
ControlProgramIndex uint64
ValidHeight uint64
Change bool2 q# ~, J `/ l4 }3 H
}+ a# }. d4 h# |
涉及utxo构造交易的相关字段说明如下:4 T/ c' |/ m( J+ q9 h$ v
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址
9 j4 }8 o) F! T6 D* a" ^
上述这些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"`
Size uint64 `json:"size"`/ t! H, o/ p( p, {: [
TimeRange uint64 `json:"time_range"`/ A0 y7 d8 c7 h; O Q& Z5 V
Inputs []*query.AnnotatedInput `json:"inputs"`1 ? {' v, Z0 B
Outputs []*query.AnnotatedOutput `json:"outputs"`
StatusFail bool `json:"status_fail"`9 S1 t, `3 Q' s5 _$ B0 Y9 ?
MuxID bc.Hash `json:"mux_id"`6 j$ f- R: C% k& @: r
}0 \# [4 I* V) ]$ x8 n5 y) @
//AnnotatedOutput means an annotated transaction output.. g5 a% }) C- t8 E9 K# X; u* n+ {
type AnnotatedOutput struct {* a) y! q j1 R* Y4 T$ s
Type string `json:"type"`9 s+ F9 v; \/ |" X% `
OutputID bc.Hash `json:"id"`
TransactionID *bc.Hash `json:"transaction_id,omitempty"`, W `; A5 m0 n
Position int `json:"position"`
AssetID bc.AssetID `json:"asset_id"`& U: L# o1 v' M$ o3 d! |
AssetAlias string `json:"asset_alias,omitempty"`
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`! ]# C/ @% |. ^+ c8 s- x
Amount uint64 `json:"amount"`
AccountID string `json:"account_id,omitempty"`# Y- @, m' R5 _4 y2 D
AccountAlias string `json:"account_alias,omitempty"`) O! |) C4 n! C
ControlProgram chainjson.HexBytes `json:"control_program"`
Address string `json:"address,omitempty"`
}
utxo跟get-block返回结果的字段对应关系如下:
`SourceID` - `json:"mux_id"`# s p9 `8 C6 a( [+ V9 U5 I
`AssetID` - `json:"asset_id"`5 |% ^+ @# G; A
`Amount` - `json:"amount"`7 S. z/ m& K: E0 K
`SourcePos` - `json:"position"`
`ControlProgram` - `json:"control_program"`
`Address` - `json:"address,omitempty"`
4.通过utxo构造交易
通过utxo构造交易就是使用spend_account_unspent_output的方式来花费指定的utxo。( y2 q" h" y0 ?/ _4 T
第一步,通过utxo构造交易输入TxInput和签名需要的数据信息SigningInstruction,该部分功能可以参考代码account/builder.go#L169进行相应改造为:+ S( q" N" j6 F$ i# y2 L3 ^- a
// UtxoToInputs convert an utxo to the txinput; @3 B) V! g: A3 ?9 ?$ |# ~4 j: M
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{}- t- Q7 ~) ?0 t( G! r* [' ?
if u.Address == "" {
return txInput, sigInst, nil
} `+ s6 Y2 C+ J* a
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)! R1 F, e8 }# e3 l+ n, e' J! d
if err != nil {
return nil, nil, err
}0 }6 R) S! c. g! a* t5 v
switch address.(type) {
case *common.AddressWitnessPubKeyHash:" c1 V* {3 d5 K$ L, w+ E6 g
derivedPK := xpubs[0].PublicKey()4 t4 z! T- h, z' D- F# |
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))5 Y% o+ j/ m" N- }& q: q5 g
case *common.AddressWitnessScriptHash:) u* M! U) ~: B W$ D
derivedPKs := chainkd.XPubKeys(xpubs)( U) ~1 V1 N6 |& H
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {8 C$ l% d, j9 e. g: T: l
return nil, nil, err( b7 R* E* k0 i4 ?' ^
}. j9 s# [: s" J, T5 Q
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
default:
return nil, nil, errors.New("unsupport address type")
}; N8 I/ O+ B) u. ~8 q& g, P& c
return txInput, sigInst, nil+ E$ R$ H/ x+ Z3 p2 w# R& k
}0 b% H/ i& `8 r* n2 x( ~. _# H
第二步,通过utxo构造交易输出TxOutput
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output struct
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {" c2 c' i) Y( _7 ^
return &TxOutput{( l" F. o' C) m! K3 Y/ B, {$ @* z
AssetVersion: 1,! B; Y: O; h# B9 r
OutputCommitment: OutputCommitment{
AssetAmount: bc.AssetAmount{0 _1 F. K6 ?/ x, U+ @. `& g: J: y$ H
AssetId: &assetID,8 g+ q& ^4 B1 j
Amount: amount,
},
VMVersion: 1,( {0 r4 p! p" q# L$ G' {( r
ControlProgram: controlProgram,( s9 I& g6 r# N. d# _
},1 ~+ l D" w9 D2 x# Q4 I
}
}. E. y7 r) ?: P P
5.组合交易的input和output构成交易模板
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:, A2 \; S8 x. i$ i( T
type InputAndSigInst struct {$ b, H' g! `$ }% j; X& H }) M; u
input *types.TxInput, ]; `' c0 M& z
sigInst *SigningInstruction$ c6 e) U. p2 b
}
// Build build transactions with template
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {( t" B& N: i6 @2 ?4 ^7 w2 ]
tpl := &Template{}
tx := &types.TxData{}* a F, O0 }- x8 i: g& K+ W
// Add all the built outputs.9 E1 [+ U/ R+ g. |/ L: f3 |
tx.Outputs = append(tx.Outputs, outputs...)
// Add all the built inputs and their corresponding signing instructions.1 b, \6 V# Z8 g7 T X. O. H
for _, in := range inputs {. F4 Y' G/ R2 @1 D1 V2 _* R
// Empty signature arrays should be serialized as empty arrays, not null.
in.sigInst.Position = uint32(len(inputs))* j, L+ \/ |; I; j
if in.sigInst.WitnessComponents == nil {
in.sigInst.WitnessComponents = []witnessComponent{}
}
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)7 q# J4 [$ W7 \2 w
tx.Inputs = append(tx.Inputs, in.input), N: d9 v5 n y8 a, a
}
tpl.Transaction = types.NewTx(*tx)# {" s) E& d& [* B5 i: B g7 \
return tpl, tx, nil6 J8 u( {' |! r( Z. ^5 u
}
6.对构造的交易进行签名5 j4 T. o+ c) ^5 H+ k
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)! |8 A f# H& f/ d; U
// Sign will try to sign all the witness
func Sign(tpl *Template, xprv chainkd.XPrv) error {
for i, sigInst := range tpl.SigningInstructions {; n* j5 C- ?; X v
h := tpl.Hash(uint32(i)).Byte32() h# D" P. u1 w% V& Z( m& r" R
sig := xprv.Sign(h[:])8 t4 T& H- Y2 B
rawTxSig := &RawTxSigWitness{5 _9 j' `0 X5 T9 \" V; ]
Quorum: 1,
Sigs: []json.HexBytes{sig},
}& D- ^: p. E9 q' Y% R+ f; s
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)9 [- N0 x/ r' U* v" ]
}
return materializeWitnesses(tpl); r: K+ x% X- W; I! ]! @" D* k
}5 v) g" z2 I+ U, ?! F: Y; W& c A
7.提交交易上链
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可) {8 s. k, s& z
成为第一个吐槽的人