Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
172
0
0
Github地址:https://github.com/Bytom/bytom5 [+ O' d' _( ^0 r- @/ B( ^. y
Gitee地址:https://gitee.com/BytomBlockchain/bytom7 w, W$ K5 D) K: [4 s9 x. @
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。0 n9 {- ?3 u& h1 K
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链4 r/ j- z& k6 R9 N! P7 ?
注意事项:
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L255
1.创建私钥和公钥
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥( l' k u! G$ M6 L" O1 T; {
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) {
xprv, err = NewXPrv(r)! ?( V: k6 {8 g; `, O0 f; }5 n* `( }; z3 `
if err != nil {# `& I' i. ]! R% H$ J7 O) M2 E
return
}& i) L7 D2 v$ ]. H0 K @
return xprv, xprv.XPub(), nil2 }. K' Q' V( p5 d2 M2 H
}% D* I' q ?- Z1 P/ P+ p
2.根据公钥创建接收对象
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:' M5 M* J2 {5 n+ \, Y& x
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {
pubKey := xpub.PublicKey()
pubHash := crypto.Ripemd160(pubKey)
// TODO: pass different params due to config; y; l+ `3 D' J3 R* b [0 U9 o" @' e
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)0 s% D, S8 K4 E" a2 K9 K2 n
if err != nil {8 j1 x0 A* U/ F+ G+ a
return nil, err
}
control, err := vmutil.P2WPKHProgram([]byte(pubHash))/ ]- c3 e5 t% B9 |; m+ X
if err != nil {
return nil, err4 {& B ^+ h4 C& Y+ g- o, G* {
}% J) a9 }9 s' q: \( h
return &CtrlProgram{- i# v& d3 C8 p0 i& n9 J( ^
Address: address.EncodeAddress(),5 h4 j8 p2 {- Q! K+ |7 S/ j
ControlProgram: control, M/ r. c( y' Y! }7 m% n
}, nil
}
创建多签地址参考代码account/accounts.go#L294进行相应改造为:' g: a% h$ w' S, ?. u: i" E8 R
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {
derivedPKs := chainkd.XPubKeys(xpubs)
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {8 k" I- \7 M, V9 b
return nil, err
}
scriptHash := crypto.Sha256(signScript)' Q& x6 `9 `# j; O! m$ D
// TODO: pass different params due to config3 X' C$ K o: F: ?( P
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams); E: A3 V' t& D" ^
if err != nil {
return nil, err5 q% m h f0 B8 h' H
}
control, err := vmutil.P2WSHProgram(scriptHash)6 w5 r7 I1 t6 }4 A
if err != nil {3 I/ L4 B( { i2 q8 b2 B
return nil, err
}, c; P p/ T- K' E9 c: @/ T `
return &CtrlProgram{
Address: address.EncodeAddress(),4 e! Q S! K( k) [
ControlProgram: control,
}, nil0 m3 b* P) P# [5 H
}0 T5 I" M D. I# |( Z/ [
3.找到可花费的utxo
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)/ k. w4 d. b5 V0 l' Q/ p* d: b
// UTXO describes an individual account utxo.
type UTXO struct {, Y ?' ~/ H6 p
OutputID bc.Hash
SourceID bc.Hash
// Avoiding AssetAmount here so that new(utxo) doesn't produce an
// AssetAmount with a nil AssetId.
AssetID bc.AssetID
Amount uint64
SourcePos uint64% y* r) e- I. C( S- T6 \
ControlProgram []byte* z6 K: h6 \7 Q0 u2 V9 \6 L9 ~
AccountID string
Address string0 D' F4 G- x8 }
ControlProgramIndex uint64
ValidHeight uint64
Change bool3 U, w6 ~" y/ U) }9 k$ e, S
}
涉及utxo构造交易的相关字段说明如下:
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址
- @0 C, k: H! |; u1 s& z* E! @3 f
上述这些utxo的字段信息可以从get-block接口返回结果的transaction中找到,其相关的结构体如下:(参考代码api/block_retrieve.go#L26)# J5 A+ ]8 I' o' B( ]2 b
// BlockTx is the tx struct for getBlock func
type BlockTx struct {
ID bc.Hash `json:"id"`
Version uint64 `json:"version"`2 ?0 z+ c# n! C. _/ [' J. c3 N- y/ V
Size uint64 `json:"size"`
TimeRange uint64 `json:"time_range"`
Inputs []*query.AnnotatedInput `json:"inputs"`
Outputs []*query.AnnotatedOutput `json:"outputs"`! F; O7 [1 v" V7 [
StatusFail bool `json:"status_fail"`
MuxID bc.Hash `json:"mux_id"`
}4 J% ^0 ?) o: N# R* ]
//AnnotatedOutput means an annotated transaction output.: V4 |3 K- Z# Y1 S. S9 S/ q
type AnnotatedOutput struct {
Type string `json:"type"`/ |* I5 B2 r9 ^4 Z3 B
OutputID bc.Hash `json:"id"`$ O! v# E9 s2 z8 C6 C6 @$ W4 D' v
TransactionID *bc.Hash `json:"transaction_id,omitempty"`
Position int `json:"position"`. t" R+ }5 {; F: `- a
AssetID bc.AssetID `json:"asset_id"`
AssetAlias string `json:"asset_alias,omitempty"`/ Q- ^, b; j7 t
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`
Amount uint64 `json:"amount"`9 e( P/ P5 m' U
AccountID string `json:"account_id,omitempty"`% d* d+ S0 r$ l6 E) k/ w
AccountAlias string `json:"account_alias,omitempty"`
ControlProgram chainjson.HexBytes `json:"control_program"`# l% K! K1 {. }; U3 y3 |
Address string `json:"address,omitempty"`
}
utxo跟get-block返回结果的字段对应关系如下:
`SourceID` - `json:"mux_id"`9 x7 M! G4 K }+ @
`AssetID` - `json:"asset_id"`
`Amount` - `json:"amount"`9 _! v* `4 K& l% k1 E* ^
`SourcePos` - `json:"position"`; ^( F- i# f5 H! v4 x) E2 |* w
`ControlProgram` - `json:"control_program"`
`Address` - `json:"address,omitempty"`
4.通过utxo构造交易
通过utxo构造交易就是使用spend_account_unspent_output的方式来花费指定的utxo。
第一步,通过utxo构造交易输入TxInput和签名需要的数据信息SigningInstruction,该部分功能可以参考代码account/builder.go#L169进行相应改造为:. ~7 D1 g$ I a& @! v5 u3 ^
// 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{}
if u.Address == "" {+ u/ {$ m/ t. G& T9 x# M' M
return txInput, sigInst, nil8 }! O0 @" s: q- [2 U
}
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
if err != nil {% a8 f2 n- g+ @5 y% O5 k5 X
return nil, nil, err
}
switch address.(type) {
case *common.AddressWitnessPubKeyHash:. B2 d! l9 V$ x9 y; w. P; }8 y
derivedPK := xpubs[0].PublicKey(). T9 L# z, T Q4 n
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))1 ]' ^3 t+ G. s- e/ Z' b4 g
case *common.AddressWitnessScriptHash:1 P5 X8 `: A m. O6 V
derivedPKs := chainkd.XPubKeys(xpubs)
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {+ f" p S6 i4 |$ `% c0 ?
return nil, nil, err
}/ Y8 q: {: M: p% U
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
default:" c6 \. b) y$ V) s: l+ x$ E
return nil, nil, errors.New("unsupport address type")8 _; j1 b, y) g
}( H3 x' `# c$ q i# S9 g
return txInput, sigInst, nil
}% C5 ]9 p- Q& ]' G
第二步,通过utxo构造交易输出TxOutput; Y0 ^" C. q* D
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output struct
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {
return &TxOutput{! }* g3 |' c: \1 N8 D
AssetVersion: 1,1 M2 b C7 }/ {2 P1 _
OutputCommitment: OutputCommitment{
AssetAmount: bc.AssetAmount{
AssetId: &assetID,) R& U/ @) j" A- K6 z$ R
Amount: amount,
},7 Y# {+ s) X1 c |9 B
VMVersion: 1,
ControlProgram: controlProgram,
},0 W9 T0 W* T" `" ]2 I$ K
}
}/ f. W8 [$ H8 d: S# f) H* E
5.组合交易的input和output构成交易模板
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:$ b! ~+ D/ O" U- O$ O" ?* E
type InputAndSigInst struct {
input *types.TxInput
sigInst *SigningInstruction5 q8 N1 e; }2 @) s9 t9 X, L
}1 m6 Q2 @0 V# K, m* R0 ~
// Build build transactions with template
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {
tpl := &Template{}2 ?0 J, }$ x. Z, v
tx := &types.TxData{}4 n" R3 E3 }+ Y
// Add all the built outputs./ G1 K3 v4 c$ y5 Z$ O0 O/ T
tx.Outputs = append(tx.Outputs, outputs...)/ {/ X: W* o3 g
// 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.& M. z+ S& H9 Z, i6 s- u3 U/ }
in.sigInst.Position = uint32(len(inputs))
if in.sigInst.WitnessComponents == nil {
in.sigInst.WitnessComponents = []witnessComponent{}
}
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)
tx.Inputs = append(tx.Inputs, in.input)
}# f/ E, A) Z5 l" V0 Y; }' t0 \
tpl.Transaction = types.NewTx(*tx)6 T! a& n8 k3 }" u1 d& n
return tpl, tx, nil
}
6.对构造的交易进行签名
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)1 H- r# u( t# u
// Sign will try to sign all the witness' Z% H( v' O: y A9 |* K+ ]+ p3 V
func Sign(tpl *Template, xprv chainkd.XPrv) error {
for i, sigInst := range tpl.SigningInstructions {3 B. [: C% M1 s2 x6 v
h := tpl.Hash(uint32(i)).Byte32()2 \: S" N) [7 `9 T& A* b
sig := xprv.Sign(h[:])
rawTxSig := &RawTxSigWitness{( i* e5 @9 c5 Q1 B6 n+ }
Quorum: 1,
Sigs: []json.HexBytes{sig},
}
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)7 ]& V6 G3 S. P& ?& h' q
}
return materializeWitnesses(tpl)6 V, o7 J5 q: |8 t
}* }/ R' Y. _+ R$ x
7.提交交易上链7 w. ?0 D' V1 c5 _; T
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可1 H) I' E) [; |& {
- C) d2 }; y* B) x: `4 s
成为第一个吐槽的人