Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
155
0
0
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。: k- J N4 M! {& n# ?7 |
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链5 k- V6 P9 r# |
! Z. w4 O @% L+ B% w
注意事项:2 G. _% _. s8 E% x% ^) Q
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L255
1.创建私钥和公钥
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥, J) o, h$ L* Q H! }$ U0 w
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) {9 n# N/ U7 i q# f! u4 H
xprv, err = NewXPrv(r)
if err != nil {7 N0 j. R M- J- }7 b
return
}; h6 }1 M6 u8 K# o; `
return xprv, xprv.XPub(), nil
} x2 q1 x7 J, Z# K" h
2.根据公钥创建接收对象
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {
pubKey := xpub.PublicKey()
pubHash := crypto.Ripemd160(pubKey)
// TODO: pass different params due to config+ {# V/ a- n4 \6 s
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
if err != nil {
return nil, err; K. l. P/ e$ l. _# ~
}" V! J5 l8 r+ z/ Q
control, err := vmutil.P2WPKHProgram([]byte(pubHash))
if err != nil {( `3 k8 A+ l. ^' ^ P" r8 z* X
return nil, err
}/ o* o8 h- P2 K F
return &CtrlProgram{* K& Z4 q/ D! S4 I
Address: address.EncodeAddress(),
ControlProgram: control,* Z# U/ [. j( W( U6 i* Y
}, nil
}
创建多签地址参考代码account/accounts.go#L294进行相应改造为:
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {4 f1 l! c' }1 S! u' o
derivedPKs := chainkd.XPubKeys(xpubs)$ H/ N; J, n: T% h O( O; n
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {7 A1 M, I8 Q. j( v* ^0 \, s: H
return nil, err
}
scriptHash := crypto.Sha256(signScript)6 ^) u% E2 s( G L1 o# s
// TODO: pass different params due to config
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams), s2 D7 q7 V% ?
if err != nil {
return nil, err
}
control, err := vmutil.P2WSHProgram(scriptHash)
if err != nil {6 n, B' {* A- l+ D
return nil, err
}
return &CtrlProgram{' v) F- ?" k, f6 A
Address: address.EncodeAddress(),
ControlProgram: control,
}, nil
}
3.找到可花费的utxo
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)4 a$ j5 l. l& v5 X
// UTXO describes an individual account utxo.. s4 Z0 X7 l5 j1 K; V
type UTXO struct {
OutputID bc.Hash
SourceID bc.Hash
// Avoiding AssetAmount here so that new(utxo) doesn't produce an
// AssetAmount with a nil AssetId.( L6 |8 H/ f8 A) O
AssetID bc.AssetID( E! ^: X" n- ]5 a
Amount uint64
SourcePos uint64
ControlProgram []byte
AccountID string
Address string; z6 ^1 p7 U2 x2 {; @' W( A' S
ControlProgramIndex uint64* @6 x/ B# o, v1 W
ValidHeight uint64
Change bool: J1 N0 T9 ]6 z, |$ c
}9 Y I% w% X9 j; I/ V- |
涉及utxo构造交易的相关字段说明如下:4 S7 h) m( H* ?" H+ |
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址
2 n, v$ J& W' G0 M' R6 y
上述这些utxo的字段信息可以从get-block接口返回结果的transaction中找到,其相关的结构体如下:(参考代码api/block_retrieve.go#L26)4 n6 s: z0 U7 ]
// BlockTx is the tx struct for getBlock func; B7 e; g0 c( E3 e2 ^% T, ]4 J$ J
type BlockTx struct {; M0 D% J8 i5 s5 y. {9 E/ L
ID bc.Hash `json:"id"`
Version uint64 `json:"version"`
Size uint64 `json:"size"`
TimeRange uint64 `json:"time_range"`
Inputs []*query.AnnotatedInput `json:"inputs"`
Outputs []*query.AnnotatedOutput `json:"outputs"`
StatusFail bool `json:"status_fail"`! S: w. M7 _$ F: I+ L2 \. y
MuxID bc.Hash `json:"mux_id"`& x4 t- j" ~" _, y+ h
}4 l- [- B$ S5 ?+ y; v% @4 @
//AnnotatedOutput means an annotated transaction output.2 [8 v5 {" z4 A/ ^
type AnnotatedOutput struct {
Type string `json:"type"`
OutputID bc.Hash `json:"id"`
TransactionID *bc.Hash `json:"transaction_id,omitempty"`7 P! z) v8 W! @, E* u5 }$ |- v- {: \
Position int `json:"position"`
AssetID bc.AssetID `json:"asset_id"`
AssetAlias string `json:"asset_alias,omitempty"`
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`5 ~' ~- s- \1 x$ Z6 z
Amount uint64 `json:"amount"`* R8 ]8 t) e1 W q( r, P" n1 U* v
AccountID string `json:"account_id,omitempty"`8 g) `! \& @# C6 N
AccountAlias string `json:"account_alias,omitempty"`( ` Y$ t5 g/ _" O6 P9 d) e
ControlProgram chainjson.HexBytes `json:"control_program"`
Address string `json:"address,omitempty"`
}
utxo跟get-block返回结果的字段对应关系如下:: c! A# x0 `2 k; D- w6 G
`SourceID` - `json:"mux_id"`5 N8 I1 l3 \& {* d
`AssetID` - `json:"asset_id"`; k+ q8 s- c/ X* P4 o
`Amount` - `json:"amount"`" \# i7 t3 w9 V4 b
`SourcePos` - `json:"position"`! U) K& o9 V" |' g$ W2 y4 L
`ControlProgram` - `json:"control_program"`
`Address` - `json:"address,omitempty"`5 i% k; z6 P; G
4.通过utxo构造交易( x# K& J' y4 K9 H: S( a3 Z7 K
通过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) {& K" Y9 t# p. ?! ?
txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)5 x8 G5 q, p6 j& Y+ b
sigInst := &txbuilder.SigningInstruction{}1 e2 p9 @# a& q: K1 W) }
if u.Address == "" {. M5 a" v! F+ a3 s8 i1 i
return txInput, sigInst, nil: `6 n* V' M' {3 R
}* f( M5 P. z# t5 h# [0 s
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
if err != nil {& L2 r. J2 O: G4 ]( i; b. R3 R: T+ K
return nil, nil, err
}0 S: T$ i Q9 ]
switch address.(type) {
case *common.AddressWitnessPubKeyHash:/ p# Y, C7 d3 T4 C5 Z
derivedPK := xpubs[0].PublicKey()
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
case *common.AddressWitnessScriptHash:4 q9 q8 m ?$ J, P, y
derivedPKs := chainkd.XPubKeys(xpubs)( f; t2 z9 i: k4 B2 {5 G8 m
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {/ B; t! W2 G* K4 d1 `
return nil, nil, err
}! y& a5 M0 o6 M: z
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))0 e9 J. O6 v8 L* ~( G" _
default:
return nil, nil, errors.New("unsupport address type")$ S1 Y$ U! ?4 W! X+ }' S S. f! m( m
}8 g% r2 `3 F/ f$ J5 o1 F, C
return txInput, sigInst, nil5 f y2 w0 \/ P
}* H# c+ F# t- L" y$ N0 v) k# ^
第二步,通过utxo构造交易输出TxOutput
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output struct5 `: `' ?" H" t# s! h# d& |
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {
return &TxOutput{' H5 }3 B; U9 J$ r$ e0 Q2 @
AssetVersion: 1,' V3 s9 G3 P7 a+ j# ]
OutputCommitment: OutputCommitment{( n8 s6 D2 w7 {" F$ Q1 x$ c/ |
AssetAmount: bc.AssetAmount{
AssetId: &assetID,) L1 Q! M$ t' j) w+ z& f
Amount: amount,. A r; Q8 r9 l$ h$ K8 D6 R
},
VMVersion: 1,
ControlProgram: controlProgram,
},
}
}
5.组合交易的input和output构成交易模板8 _: G2 P- `( L; k# E& J
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:
type InputAndSigInst struct {
input *types.TxInput t# K4 z1 Q8 |4 i: S
sigInst *SigningInstruction4 h+ |1 _7 }: k! E9 B! W; z
}+ ~0 r7 Y7 T7 F: e+ r) e9 b
// Build build transactions with template
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {+ A0 r& \5 N8 R% i
tpl := &Template{}
tx := &types.TxData{}
// Add all the built outputs.# o* G( n" `) Q/ I9 S
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.2 n4 o0 h( R0 l; @
in.sigInst.Position = uint32(len(inputs))
if in.sigInst.WitnessComponents == nil {- W* @+ A0 l: o7 m. ?
in.sigInst.WitnessComponents = []witnessComponent{}
}
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)# X. l& v9 j' s# u) Z5 J
tx.Inputs = append(tx.Inputs, in.input)- v9 Z$ t# d% {1 T& T
}) [. u4 }3 ^6 c
tpl.Transaction = types.NewTx(*tx)
return tpl, tx, nil, W8 Y2 w& o/ R$ L
}
6.对构造的交易进行签名
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)
// Sign will try to sign all the witness
func Sign(tpl *Template, xprv chainkd.XPrv) error {) Z) s1 z0 J. K) Z
for i, sigInst := range tpl.SigningInstructions {( H. b1 W% m/ m" n# [" Y3 O
h := tpl.Hash(uint32(i)).Byte32()0 ~ H. F4 {) H+ @% y. W& P% i8 V
sig := xprv.Sign(h[:]). a% H: O! Q" L
rawTxSig := &RawTxSigWitness{- i2 _+ O; H" S" e5 O3 i4 `2 f& Q
Quorum: 1,
Sigs: []json.HexBytes{sig},/ S$ Y' F! [' T) J
}
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)+ s' L' E. A ^5 B- b* \4 o
}
return materializeWitnesses(tpl)
}/ k0 F6 w, n8 B2 [6 K6 X
7.提交交易上链
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可
: o: p5 B; q! V+ Q
成为第一个吐槽的人