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

简介:

比原项目仓库:

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可以定位到前一笔交易的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的功能即可

相关文章
|
8月前
|
Python
秒合约交易系统开发技术代码研发方案丨时间盘合约交易系统开发
秒合约交易系统开发技术代码研发方案丨时间盘合约交易系统开发
|
8月前
|
区块链
DAPP合约代币博饼交易质押模式系统开发|详情方案|规则明细
智能合约是什么?它是近年来随着区块链技术的发展而崭露头角的一个概念
|
8月前
|
安全 API 区块链
合约跟单交易开发介绍
合约交易是一种高收益、高风险的投资方式,适合有一定风险承受能力和投资经验的投资者。
|
区块链
AVAX合约代币质押模式系统开发
“智能合约是一套以数字形式定义的承诺,包括合约参与方可以在上面执行这些承诺的协议”。
|
供应链 安全 区块链
区块链钱包合约代币质押系统开发(模式详情)
一组条件在时间的推移中不可能一直正确的,而智能合约是不可变的,更新当前的预编程条件几乎是不可能的
|
区块链 Python
虚拟数字货币合约交易系统开发——现货跟单交易合约源代码详情
class SpotFollower: def __init__(self, symbol, amount): self.symbol = symbol self.amount = amount
|
存储 编译器 uml
创建一个银行账户的继承层次,表示银行的所有客户的账户。每个客户都能在他们的银行账户存钱,取钱。但是账户可以分为更具体的两种类型,例如,依靠存款生息的存储账户SavingsAccount类,另一种就是信
创建一个银行账户的继承层次,表示银行的所有客户的账户。每个客户都能在他们的银行账户存钱,取钱。但是账户可以分为更具体的两种类型,例如,依靠存款生息的存储账户SavingsAccount类,另一种就是信
195 0
|
存储 JSON 数据可视化
NFT拍卖交易系统开发数据存储
  非同质化意味着它是独一无二的,这与ERC20代币截然不同,后者都是同质化代币。这些NFT可以通过编程来完成几乎任何你想做的事情,就像普通的智能合约一样,但它们具有链上加密的真实性,因为它们的部署历史将随着区块链的存在而得到几乎永久的保证。
NFT拍卖交易系统开发数据存储
|
安全 开发者
新能力|分账明白,账本清楚,商家超轻松
目前,商家分账 功能已经全面开放给企业商户,快来了解一下吧。
2542 12
新能力|分账明白,账本清楚,商家超轻松
Bytom储蓄分红合约解析
储蓄分红合约简介 储蓄分红合约指的是项目方发起了一个锁仓计划(即储蓄合约和取现合约),用户可以在准备期自由选择锁仓金额参与该计划,等到锁仓到期之后还可以自动获取锁仓的利润。用户可以在准备期内(dueBlockHeight)参与储蓄,按照合约规定可以 1:1 获取同等数量的储蓄票据资产,同时用户锁仓的资产(deposit)将放到取现合约中,并且项目方是无法动用的,等到锁仓期限(expireBlockHeight)一到,用户便可以调用取现合约将自己储蓄的资产连本待息一同取出来。
1320 0

热门文章

最新文章