V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Bytom
V2EX  ›  区块链

Bytom 交易说明(UTXO 用户自己管理模式)

  •  
  •   Bytom · 2018-08-24 10:49:28 +08:00 · 1399 次点击
    这是一个创建于 2318 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比原项目仓库:

    Github 地址: https://github.com/Bytom/bytom

    Gitee 地址: https://gitee.com/BytomBlockchain/bytom

    该部分主要针对用户自己管理私钥和地址,并通过 utxo 来构建和发 s 送交易。

    注意事项:

    以下步骤以及功能改造仅供参考,具体代码实现需要用户根据实际情况进行调试,具体可以参考单元测试案例代码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 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的功能即可

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2793 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 07:01 · PVG 15:01 · LAX 23:01 · JFK 02:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.