比原專案倉庫:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
該部分主要針對使用者自己管理私鑰和地址,並通過utxo來構建和傳送交易。
注意事項:
以下步驟以及功能改造僅供參考,具體程式碼實現需要使用者根據實際情況進行除錯,具體可以參考單元測試案例程式碼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可以定位到前一筆交易的outputAssetID
utxo的資產IDAmount
utxo的資產數目SourcePos
該utxo在前一筆交易的output的位置ControlProgram
utxo的接收programAddress
utxo的接收地址
上述這些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"`
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 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 == "" {
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 struct
func 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 template
func 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 witness
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{
Quorum: 1,
Sigs: []json.HexBytes{sig},
}
sigInst.WitnessComponents = append([]witnessComponent(rawTxSig), sigInst.WitnessComponents...)
}
return materializeWitnesses(tpl)
}
7.提交交易上鍊
該步驟無需更改任何內容,直接參照wiki中提交交易的APIsubmit-transaction的功能即可