Bytom交易说明(UTXO用户自己管理模式)
朋友一起走
发表于 2022-11-13 23:52:55
109
0
0
Github地址:https://github.com/Bytom/bytom9 E4 V# B, T! `
Gitee地址:https://gitee.com/BytomBlockchain/bytom
该部分主要针对用户自己管理私钥和地址,并通过utxo来构建和发送交易。
1.创建私钥和公钥2.根据公钥创建接收对象3.找到可花费的utxo4.通过utxo构造交易5.组合交易的input和output构成交易模板6.对构造的交易进行签名7.提交交易上链
注意事项:
以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码blockchain/txbuilder/txbuilder_test.go#L2553 a1 z( r. E3 h
1.创建私钥和公钥
该部分功能可以参考代码crypto/ed25519/chainkd/util.go#L11,可以通过 NewXKeys(nil) 创建主私钥和主公钥0 ]( m1 `1 O( G
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) {: I1 D0 O( [# p! h0 K
xprv, err = NewXPrv(r)6 U# z4 P. P% F
if err != nil {
return7 x6 s5 e2 m/ j2 _) Q7 w
}7 a" V6 u1 t9 u L1 c9 {
return xprv, xprv.XPub(), nil
}
2.根据公钥创建接收对象, h! z6 U3 A6 l+ }) N, \
接收对象包含两种形式:address形式和program形式,两者是一一对应的,任选其一即可。其中创建单签地址参考代码account/accounts.go#L267进行相应改造为:
func (m *Manager) createP2PKH(xpub chainkd.XPub) (*CtrlProgram, error) {
pubKey := xpub.PublicKey()2 i: h' _ F; t% \
pubHash := crypto.Ripemd160(pubKey)
// TODO: pass different params due to config
address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)) E! D! U- _9 j9 F, y: r, F7 C' Q' u
if err != nil {
return nil, err
}1 n1 M9 B9 g0 x& C
control, err := vmutil.P2WPKHProgram([]byte(pubHash))
if err != nil {
return nil, err* P5 b3 o5 H9 U1 M/ ~2 s
}3 m7 Y! \0 S. r. o/ e" F
return &CtrlProgram{
Address: address.EncodeAddress(),
ControlProgram: control,# D: N+ F# N! ~$ s
}, nil' ~7 _' }. h) Z, g
}+ _. Y% g0 f" X' S
创建多签地址参考代码account/accounts.go#L294进行相应改造为:8 n3 u' G4 T! x9 R2 G
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) {
derivedPKs := chainkd.XPubKeys(xpubs)% B# I( f: n( b9 c
signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs)). {, T& J5 G0 Z+ g' b
if err != nil {2 G( W/ O4 V" A. U
return nil, err
}
scriptHash := crypto.Sha256(signScript)% S2 C: W) ~: n0 S/ ?% \9 x, R
// TODO: pass different params due to config
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)9 j e: _- ?- j6 M O! R! b
if err != nil {
return nil, err; A( j" {/ n6 U4 y, x
}
control, err := vmutil.P2WSHProgram(scriptHash)" w3 J8 K t) S" h7 h
if err != nil {. r A3 ]* \3 c: _& h) a2 ]2 S
return nil, err9 z3 g0 _7 ~* t" U2 @+ O+ t* c
}
return &CtrlProgram{. a* A+ Y4 R7 B- ^# O: q+ v
Address: address.EncodeAddress(),
ControlProgram: control,8 z- r! c8 F. g8 q6 J) R
}, nil
}
3.找到可花费的utxo7 z; N+ ^6 B& L9 Y& g b/ _+ }6 [9 f, i
找到可花费的utxo,其实就是找到接收地址或接收program是你自己的unspend_output。其中utxo的结构为:(参考代码account/reserve.go#L39)
// UTXO describes an individual account utxo.' X9 P) b5 o. |
type UTXO struct {
OutputID bc.Hash
SourceID bc.Hash
// Avoiding AssetAmount here so that new(utxo) doesn't produce an1 k! p$ }, u; P; N; B. W6 N; x
// AssetAmount with a nil AssetId.
AssetID bc.AssetID
Amount uint64
SourcePos uint64, Q% j$ z/ j* V: b! _7 {: J
ControlProgram []byte
AccountID string
Address string
ControlProgramIndex uint643 @ l& t. a- [2 X5 B
ValidHeight uint64* `/ Q2 `1 h$ B7 r% \8 f
Change bool4 |* p l' V, f9 \/ R0 f! @: z/ f
}
涉及utxo构造交易的相关字段说明如下:
SourceID 前一笔关联交易的mux_id, 根据该ID可以定位到前一笔交易的outputAssetID utxo的资产IDAmount utxo的资产数目SourcePos 该utxo在前一笔交易的output的位置ControlProgram utxo的接收programAddress utxo的接收地址
上述这些utxo的字段信息可以从get-block接口返回结果的transaction中找到,其相关的结构体如下:(参考代码api/block_retrieve.go#L26)) \1 i6 K3 a- w$ M" L6 V% ]+ B! N
// BlockTx is the tx struct for getBlock func
type BlockTx struct {# o" O1 }2 [4 |
ID bc.Hash `json:"id"`
Version uint64 `json:"version"`/ `6 b8 L3 C# d5 X2 R
Size uint64 `json:"size"` t/ Z D* n$ M
TimeRange uint64 `json:"time_range"`
Inputs []*query.AnnotatedInput `json:"inputs"`/ H1 J( a: K' O+ a3 J1 K; q
Outputs []*query.AnnotatedOutput `json:"outputs"`: I3 L; I E+ g8 e
StatusFail bool `json:"status_fail"`+ |, m" P: H5 k5 N6 F' \$ u
MuxID bc.Hash `json:"mux_id"`, n. @9 |/ ]4 I5 D9 R* A% `' X
}
//AnnotatedOutput means an annotated transaction output.
type AnnotatedOutput struct {
Type string `json:"type"`
OutputID bc.Hash `json:"id"`% h' L. z. `" ?# c
TransactionID *bc.Hash `json:"transaction_id,omitempty"`
Position int `json:"position"`
AssetID bc.AssetID `json:"asset_id"`% v9 h P: m9 E: a9 R: H5 _# p
AssetAlias string `json:"asset_alias,omitempty"`
AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`
Amount uint64 `json:"amount"`
AccountID string `json:"account_id,omitempty"`1 J" y3 a0 d" @' o" D4 {
AccountAlias string `json:"account_alias,omitempty"`; ]& M3 v# a! B) X
ControlProgram chainjson.HexBytes `json:"control_program"`( J7 j3 X5 H1 y- `9 ]5 H4 p
Address string `json:"address,omitempty"`8 c! x: L5 ?2 W$ ~8 C1 c
}
utxo跟get-block返回结果的字段对应关系如下:2 C1 }( B* Z% b3 c: E
`SourceID` - `json:"mux_id"`
`AssetID` - `json:"asset_id"`
`Amount` - `json:"amount"`
`SourcePos` - `json:"position"`
`ControlProgram` - `json:"control_program"`0 C% k& Y* j3 A8 Q0 a% U: p7 P
`Address` - `json:"address,omitempty"`9 {1 U( D9 s5 I, j
4.通过utxo构造交易& L( _! u' _4 a0 l* i
通过utxo构造交易就是使用spend_account_unspent_output的方式来花费指定的utxo。4 _& k: H( a% b5 r. c2 e+ ~
第一步,通过utxo构造交易输入TxInput和签名需要的数据信息SigningInstruction,该部分功能可以参考代码account/builder.go#L169进行相应改造为:; S9 y1 T/ V0 h, V5 O
// UtxoToInputs convert an utxo to the txinput
func UtxoToInputs(xpubs []chainkd.XPub, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {+ K4 f, C4 _! n, J5 x' P
txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
sigInst := &txbuilder.SigningInstruction{}8 ` U; o7 B) V0 w
if u.Address == "" {; H" E) |; R0 I. o$ s7 E
return txInput, sigInst, nil0 V# {% W D/ v3 Y: Z1 e
}
address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams): ^& s3 ?. V7 m$ M- G% |# P
if err != nil {
return nil, nil, err. s% |" p* O, o2 p" w* e, s: {$ G
}8 b- r, a6 r4 [7 n( g+ }
switch address.(type) {( w8 M0 h9 p: Q! z
case *common.AddressWitnessPubKeyHash:+ ]* k6 \: X( }( ]* X
derivedPK := xpubs[0].PublicKey()
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
case *common.AddressWitnessScriptHash:
derivedPKs := chainkd.XPubKeys(xpubs): X$ p* \. X0 e) Z/ O6 Y
script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs))
if err != nil {
return nil, nil, err0 H. l: s6 {" L3 x) V
}" S3 ]; r/ y* r) T1 T
sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
default:# Z* q- q) T. g9 s; b, `
return nil, nil, errors.New("unsupport address type")
}
return txInput, sigInst, nil& k/ b7 S1 y y* `/ R! _( a
}
第二步,通过utxo构造交易输出TxOutput1 Q0 U. m6 O% l
该部分功能可以参考代码protocol/bc/types/txoutput.go#L20:' _' d) Y2 i0 _4 d7 u j. N
// NewTxOutput create a new output struct
func NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput {1 w K9 @) K6 s0 n; [
return &TxOutput{2 A) I/ n. v. F' J- D
AssetVersion: 1,7 U4 i$ E6 z/ H
OutputCommitment: OutputCommitment{
AssetAmount: bc.AssetAmount{4 d, z* I; @# f3 x" M1 r( X. Y: q
AssetId: &assetID,' [" ~: X& d8 L% x8 L1 x! D
Amount: amount,* Z4 z2 F! k8 H7 G
},
VMVersion: 1,4 _5 c% h0 ^6 ]6 g" }) ?
ControlProgram: controlProgram,
},
}
}
5.组合交易的input和output构成交易模板
通过上面已经生成的交易信息构造交易txbuilder.Template,该部分功能可以参考blockchain/txbuilder/builder.go#L92进行改造为:
type InputAndSigInst struct {+ w; O2 }4 d9 N+ A/ G' \
input *types.TxInput
sigInst *SigningInstruction( W( R+ d: P7 X' Q. ~/ a
}
// Build build transactions with template
func BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) {
tpl := &Template{}9 Y g1 t7 c/ ~/ [& p0 Y" O8 ]
tx := &types.TxData{}% `: z& \: q% z K1 @" ]! o
// Add all the built outputs.' o/ x+ m$ ^3 U# c2 r! N+ w
tx.Outputs = append(tx.Outputs, outputs...)
// Add all the built inputs and their corresponding signing instructions.
for _, in := range inputs {7 h/ X& j: b9 C7 V+ e
// Empty signature arrays should be serialized as empty arrays, not null.5 _/ {& {3 A3 }. q. t7 J
in.sigInst.Position = uint32(len(inputs))
if in.sigInst.WitnessComponents == nil {
in.sigInst.WitnessComponents = []witnessComponent{}+ T N% w w4 l9 W/ U* b( J
}
tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst)
tx.Inputs = append(tx.Inputs, in.input)
}
tpl.Transaction = types.NewTx(*tx)5 ]: \2 t- m$ I" X7 U+ K. q7 f& S3 J A7 v
return tpl, tx, nil
}) K6 n3 H9 q$ l
6.对构造的交易进行签名
账户模型是根据密码找到对应的私钥对交易进行签名,这里用户可以直接使用私钥对交易进行签名,可以参考签名代码blockchain/txbuilder/txbuilder.go#L82进行改造为:(以下改造仅支持单签交易,多签交易用户可以参照该示例进行改造)
// Sign will try to sign all the witness& j* S( v8 c+ k% m
func Sign(tpl *Template, xprv chainkd.XPrv) error {
for i, sigInst := range tpl.SigningInstructions {
h := tpl.Hash(uint32(i)).Byte32()
sig := xprv.Sign(h[:])
rawTxSig := &RawTxSigWitness{1 J0 n. y1 a4 q8 @- _
Quorum: 1,/ B+ j2 |3 }) S5 e
Sigs: []json.HexBytes{sig},
}
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)
}" f0 ]/ Z- S5 C7 M
return materializeWitnesses(tpl)
}) h9 Z5 n& d! Y8 V: Z ^
7.提交交易上链
该步骤无需更改任何内容,直接参照wiki中提交交易的APIsubmit-transaction的功能即可
1 J7 L5 T6 W! S$ W" R4 F' {. ?9 e
成为第一个吐槽的人