比原項目倉庫:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockc...
該部分主要針對使用者自己管理私密金鑰和地址,並通過utxo來構建和發送交易。
- 1.建立私密金鑰和公開金鑰
- 2.根據公開金鑰建立接收對象
- 3.找到可花費的
utxo
- 4.通過
utxo
構造交易
- 5.組合交易的
input
和output
構成交易模板
- 6.對構造的交易進行簽名
- 7.提交交易上鏈
注意事項:
以下步驟以及功能改造僅供參考,具體代碼實現需要使用者根據實際情況進行調試,具體可以參考單元測試案例代碼blockchain/txbuilder/txbuilder_test.go#L255
1.建立私密金鑰和公開金鑰
該部分功能可以參考代碼crypto/ed25519/chainkd/util.go#L11,可以通過 NewXKeys(nil)
建立主私密金鑰和主公開金鑰
func NewXKeys(r io.Reader) (xprv XPrv, xpub XPub, err error) { xprv, err = NewXPrv(r) if err != nil { return } return xprv, xprv.XPub(), nil}
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 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams) if err != nil { return nil, err } control, err := vmutil.P2WPKHProgram([]byte(pubHash)) if err != nil { return nil, err } return &CtrlProgram{ Address: address.EncodeAddress(), ControlProgram: control, }, nil}
建立多簽地址參考代碼account/accounts.go#L294進行相應改造為:
func (m *Manager) createP2SH(xpubs []chainkd.XPub) (*CtrlProgram, error) { derivedPKs := chainkd.XPubKeys(xpubs) signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs)) if err != nil { return nil, err } scriptHash := crypto.Sha256(signScript) // TODO: pass different params due to config address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams) if err != nil { return nil, err } control, err := vmutil.P2WSHProgram(scriptHash) if err != nil { return nil, err } return &CtrlProgram{ Address: address.EncodeAddress(), ControlProgram: control, }, nil}
3.找到可花費的utxo
找到可花費的utxo,其實就是找到接收地址或接收program
是你自己的unspend_output
。其中utxo的結構為:(參考代碼account/reserve.go#L39)
// UTXO describes an individual account utxo.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. AssetID bc.AssetID Amount uint64 SourcePos uint64 ControlProgram []byte AccountID string Address string ControlProgramIndex uint64 ValidHeight uint64 Change bool}
涉及utxo構造交易的相關欄位說明如下:
SourceID
前一筆關聯交易的mux_id, 根據該ID可以定位到前一筆交易的output
AssetID
utxo的資產ID
Amount
utxo的資產數目
SourcePos
該utxo在前一筆交易的output的位置
ControlProgram
utxo的接收program
Address
utxo的接收地址
上述這些utxo的欄位資訊可以從get-block
介面返回結果的transaction中找到,其相關的結構體如下:(參考代碼api/block_retrieve.go#L26)
// BlockTx is the tx struct for getBlock functype BlockTx struct { 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"` MuxID bc.Hash `json:"mux_id"`}//AnnotatedOutput means an annotated transaction output.type AnnotatedOutput struct { Type string `json:"type"` OutputID bc.Hash `json:"id"` TransactionID *bc.Hash `json:"transaction_id,omitempty"` Position int `json:"position"` AssetID bc.AssetID `json:"asset_id"` AssetAlias string `json:"asset_alias,omitempty"` AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"` Amount uint64 `json:"amount"` AccountID string `json:"account_id,omitempty"` AccountAlias string `json:"account_alias,omitempty"` ControlProgram chainjson.HexBytes `json:"control_program"` Address string `json:"address,omitempty"`}
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 txinputfunc 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 == "" { return txInput, sigInst, nil } address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams) if err != nil { return nil, nil, err } switch address.(type) { case *common.AddressWitnessPubKeyHash: derivedPK := xpubs[0].PublicKey() sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK))) case *common.AddressWitnessScriptHash: derivedPKs := chainkd.XPubKeys(xpubs) script, err := vmutil.P2SPMultiSigProgram(derivedPKs, len(derivedPKs)) if err != nil { return nil, nil, err } sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script)) default: return nil, nil, errors.New("unsupport address type") } return txInput, sigInst, nil}
第二步,通過utxo
構造交易輸出TxOutput
該部分功能可以參考代碼protocol/bc/types/txoutput.go#L20:
// NewTxOutput create a new output structfunc NewTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *TxOutput { return &TxOutput{ AssetVersion: 1, OutputCommitment: OutputCommitment{ AssetAmount: bc.AssetAmount{ AssetId: &assetID, Amount: amount, }, VMVersion: 1, ControlProgram: controlProgram, }, }}
5.組合交易的input和output構成交易模板
通過上面已經產生的交易資訊構造交易txbuilder.Template
,該部分功能可以參考blockchain/txbuilder/builder.go#L92進行改造為:
type InputAndSigInst struct { input *types.TxInput sigInst *SigningInstruction}// Build build transactions with templatefunc BuildTx(inputs []InputAndSigInst, outputs []*types.TxOutput) (*Template, *types.TxData, error) { tpl := &Template{} tx := &types.TxData{} // Add all the built outputs. 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 { in.sigInst.WitnessComponents = []witnessComponent{} } tpl.SigningInstructions = append(tpl.SigningInstructions, in.sigInst) tx.Inputs = append(tx.Inputs, in.input) } tpl.Transaction = types.NewTx(*tx) return tpl, tx, nil}
6.對構造的交易進行簽名
賬戶模型是根據密碼找到對應的私密金鑰對交易進行簽名,這裡使用者可以直接使用私密金鑰對交易進行簽名,可以參考簽名代碼blockchain/txbuilder/txbuilder.go#L82進行改造為:(以下改造僅支援單簽交易,多簽交易使用者可以參照該樣本進行改造)
// Sign will try to sign all the witnessfunc 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{ Quorum: 1, Sigs: []json.HexBytes{sig}, } sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...) } return materializeWitnesses(tpl)}
7.提交交易上鏈
該步驟無需更改任何內容,直接參照wiki中提交交易的APIsubmit-transaction的功能即可