BTC交易流程

交易流程图

image

交易说明

交易的创建
  1. 遍历自己地址的可用UTXO是否大于交易的数额amount。

  2. 计算交易费用fee

  3. 如果如果可用UTXO >= fee + amount 则开始组织交易。

    1. 生成输出 一个一个的 TxOut,并添加到数组中 (包含转账地址和找零地址)。

    2. 根据UTXO生成 TxIn,并添加到数组中。

    3. 对 TxIn 数组中的 TxIn 进行逐一签名。

    4. 发送交易信息。

交易的输入

每一笔交易都会由一个或一组输入和一个或一组输出组成,除了一种特殊的交易 coinbase,coinbase是bitcoin-core定义的给区块挖出者的奖励,其实也是比特币的产出过程。只有coinbase交易会产生比特币,其他的交易都是比特币的转移。

一个交易(MsgTx)是由多个Input和多个Output组成的,而在Input中是由指向UTXO的OutPoint,解锁脚本SignatureScript和序列Sequence组成,一笔交易分为三个步骤:
  1. 构建原始交易RawTransaction,该交易包含了输入指向的OutPoint,也包含了完整的Output,但是没有签名,也就是没有设置SignatureScript的内容。

  2. 用私钥对签名构建的RawTransaction进行签名,并将签名构建成完整的解锁脚本,填入对应的Input的SignatureScript字段中。

  3. 将签名后的Transaction发送到P2P网络中。

对交易进行签名,有几个Input,就要要签名几次,每个签名的原理是一样的,在比特币中,对一笔交易的签名流程是这样的:
  1. 查找该笔交易对应的UTXO

  2. 获得该UTXO对应的锁定脚本

  3. 复制该交易对象,并在复制副本中将该Input的解锁脚本字段的值设置为对应的锁定脚本

未花费的消费列表

│    {

│       "output_no": 0,

│       "scriptPubKey": "76a914b73187dbcab54a93d861f1e53b39843f8a8d8ce988ac",

│       "address": "1HhdzZNfAdpdtbGnRNkat9YSc5BN6TKenG",

│       "txid": "ac0472c6a1095ffd2372412ba1c88c291c25b2315fedcdb45d02189fc2436c06",

│       "value": "0.00003499",

│       "satoshis": "3499"

│     }

比特币交易数据解析

签名的信息

01000000024a2989e933ed9875f83776ddb4ee105a3ee7363e60e0f53db8a682f82aa52303010000006b483045022071e572d11357a0801ce69741ffde20de49099cd4ddeac19d4ad6697ca766c5fc022100835d7dabdc674e78ca523f09811b7178b3c31d08735909a28975721c6a2d2e34012103be47f412d8e23a3baf591bbe42c5859062b5b0b168567ecd8746ddc1948103fdffffffff0bcdf46d36a33c8d768e25be6b185bc9efad8aca7fad55b5180c9475afe6718f000000006c493046022100ae57aeb9711a6879bc7ddd363feb62aa5aa42b0dca6aa81e1eafc533e4cce0c4022100db46f11ac362e0fe494c65d24fb702c92ebc554690a7447365d38e7aa3613a740121030b7979840ce44d7318f5883aed9d8f5a3c0778326beae0c2117ea8712c7de5aaffffffff0108880200000000001976a9148c2cbf83f9b3034007456580a9072a9304684a4988ac00000000

签名信息解析

{

    "addresses": [

        "1DnBD1YD8HhDdneKUUh36TK48pTWVKkWvC"

    ],

    "block_height": -1,

    "block_index": -1,

    "confirmations": 0,

    "double_spend": false,

    "fees": 0,

    "hash": "a0b956a450134ede9839c4d9fcf4c31446a24a135c2f8c11a8288eac0ba1a34f",

    "inputs": [

        {

            "age": 0,

            "output_index": 1,

            "prev_hash": "0323a52af882a6b83df5e0603e36e73e5a10eeb4dd7637f87598ed33e989294a",

            "script": "483045022071e572d11357a0801ce69741ffde20de49099cd4ddeac19d4ad6697ca766c5fc022100835d7dabdc674e78ca523f09811b7178b3c31d08735909a28975721c6a2d2e34012103be47f412d8e23a3baf591bbe42c5859062b5b0b168567ecd8746ddc1948103fd",

            "script_type": "empty",

            "sequence": 4294967295

        },

        {

            "age": 0,

            "output_index": 0,

            "prev_hash": "8f71e6af75940c18b555ad7fca8aadefc95b186bbe258e768d3ca3366df4cd0b",

            "script": "493046022100ae57aeb9711a6879bc7ddd363feb62aa5aa42b0dca6aa81e1eafc533e4cce0c4022100db46f11ac362e0fe494c65d24fb702c92ebc554690a7447365d38e7aa3613a740121030b7979840ce44d7318f5883aed9d8f5a3c0778326beae0c2117ea8712c7de5aa",

            "script_type": "empty",

            "sequence": 4294967295

        }

    ],

    "outputs": [

        {

            "addresses": [

                "1DnBD1YD8HhDdneKUUh36TK48pTWVKkWvC"

            ],

            "script": "76a9148c2cbf83f9b3034007456580a9072a9304684a4988ac",

            "script_type": "pay-to-pubkey-hash",

            "value": 165896

        }

    ],

    "preference": "low",

    "received": "2020-06-15T08:47:43.844912895Z",

    "relayed_by": "3.92.56.73",

    "size": 341,

    "total": 165896,

    "ver": 1,

    "vin_sz": 2,

    "vout_sz": 1

}

签名交易流程

private BtcTxSignResult signTransaction(String password, Wallet wallet, String to, boolean isFork) {

    // 发送到的地址

    this.to = to;

    // NetworkParameters上下文

    this.network = NetParamsService.getNetworkParameters(wallet.chainType);

    // 获取私钥信息

    byte[] bytes = WalletManager.getBtcPriKey(wallet.keystore, password);

    String xprv = new String(bytes, Charset.forName("UTF-8"));

    DeterministicKey xprvKey = DeterministicKey.deserializeB58(xprv, network);

    BigInteger privateKey = xprvKey.getPrivKey();

    ECKey ecKey = ECKey.fromPrivate(privateKey);

    // 创建交易信息实体

    Transaction tran = new Transaction(network);



    long changeAmount = totalMoney - amount - fee;

    LogUtils.eTag("UTXOTransaction", changeAmount + "totalMoney:" + totalMoney + "amount:" + amount + "fee:" + fee);

    // 输出地址

    tran.addOutput(Coin.valueOf(amount), Address.fromString(network, this.to));

    // 找零地址

    if (changeAmount > DUST_THRESHOLD) {

        tran.addOutput(Coin.valueOf(changeAmount), Address.fromString(network, wallet.getAddress()));

    }

    // 输入信息

    for (UTXO output : getOutputs()) {

        TransactionOutPoint transactionOutPoint = new TransactionOutPoint(network, output.getVout(), Sha256Hash.wrap(output.getTxHash()));

        //这个添加签名输入的最后一个参数就是添加了SIGHASH_FORKID(0x40)

        tran.addSignedInput(transactionOutPoint, Coin.valueOf(new BigDecimal(output.getAmount()).longValue())

                , new Script(NumericUtil.hexToBytes(output.getScriptPubKey())), ecKey, Transaction.SigHash.ALL, true, isFork);

    }

    // 生产交易hash

    String signedHex = NumericUtil.bytesToHex(tran.bitcoinSerialize());

    String txHash = NumericUtil.beBigEndianHex(Hash.sha256(Hash.sha256(signedHex)));

    return new BtcTxSignResult(signedHex, txHash);

}

获取NetworkParametersNetworkParameters说明

不同的币种NetworkParameters的里面的两个参数不同

    int addressHeader = 0;

    case LTC:

        return 0x30;

    case BCH:

    case BTC:

    case EOS:

        return 0x00;

    case DASH:

        return 0x4C;

    case DOGE:

        return 0x1E;

    int bip32HeaderP2PKHpriv

    case LTC:

        return 0x019D9CFE;

    case BCH:

    case BTC:

    case EOS:

        return 0x0488ADE4;

    case DASH:

        return 0x02FE52CC;

    case DOGE:

        return 0x0488E1F4;

UTXO数据UTXO的定义

新的交易模块设计:

/**

 * @FileName     : TransactionModule

 * @date         : 2020/6/15 10:46

 * @author       : Owen

 * @description  : 交易相关接口类

 */

object TransactionModule {

    open class ValidationError : Exception() {

        class EmptyValue() : ValidationError()

        class InsufficientBalance() : ValidationError()

        class NotEnoughForMinimumRequiredBalance() : ValidationError()

        class TooFewAmount() : ValidationError()

        class MaxAmountLimit() : ValidationError()

    }



    /**

     * 转账金额处理

     */

    interface IAmountDelegate {



        val availableBalance: BigDecimal

        var inputAmount: BigDecimal



        fun setInputAmount(amount: String)

        @Throws

        fun validAmount(): Boolean

    }



    /**

     * 发送到的地址处理

     */

    interface IAddressDelegate {

        fun setToAddress(toAddress: String)

        @Throws

        fun validAddress(): Boolean

    }



    /**

     * 转账费用处理

     */

    interface IFeeDelegate {



        fun loadData()

        @Throws

        fun validFee(): Boolean



        fun onClear()



    }



    /**

     * 广播交易处理

     */

    interface ISendDelegate {

        fun signTransaction()

        fun sendTransaction()

    }



    /**

     * 发送交易流程代理

     */

    interface ITransactionDelegate {



        fun loadData()

        fun setInputAmount(amount: String)

        fun setToAddress(toAddress: String)

        fun calculateFee()

        fun setMemo(memo: String)

        fun validTransactionData()

        fun sendTransaction()



    }



    class Factory(private val wallet: Wallet) : ViewModelProvider.Factory {

        override fun <T : ViewModel?> create(modelClass: Class<T>): T {

            val presenter = TransactionPresenter()

            when(wallet.chainType){

                CoinEnum.BTC.symbol,

                CoinEnum.BTC.symbol,

                CoinEnum.BTC.symbol,

                CoinEnum.BTC.symbol -> {

                }

                CoinEnum.ETH.symbol -> {



                }

                CoinEnum.EOS.symbol -> {



                }



            return presenter as T

        }



    }

}

/**

 * @FileName     : AmountDelegate

 * @date         : 2020/6/15 11:25

 * @author       : Owen

 * @description  : Amount交易金额代理类

 */

class AmountDelegate(balance: String) : TransactionModule.IAmountDelegate {

    override val availableBalance: BigDecimal

        get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.



    override var inputAmount: BigDecimal = BigDecimal.ZERO



    override fun setInputAmount(amount: String) {



    }



    override fun validAmount(): Boolean {

        return true

    }

}

/**

 * @FileName     : TransactionPresenter

 * @date         : 2020/6/15 10:56

 * @author       : Owen

 * @description  : 交易代理实现

 */

class TransactionPresenter : ViewModel(), TransactionModule.ITransactionDelegate {

    private var memo: String = ""



    private val errorMessage = MutableLiveData<String>()



    var amountDelegate: TransactionModule.IAmountDelegate? = null

    var addressDelegate: TransactionModule.IAddressDelegate? = null

    var feeDelegate: TransactionModule.IFeeDelegate? = null

    var sendDelegate: TransactionModule.ISendDelegate? = null



    override fun loadData() {

        feeDelegate?.loadData()

    }



    override fun setInputAmount(amount: String) {

        amountDelegate?.setInputAmount(amount)

    }



    override fun setToAddress(toAddress: String) {

        addressDelegate?.setToAddress(toAddress)

    }



    override fun calculateFee() {



    }



    override fun setMemo(memo: String) {

        this.memo = memo

    }



    override fun validTransactionData() {

        try {

            amountDelegate?.validAmount()

            addressDelegate?.validAddress()

            feeDelegate?.validFee()

            sendTransaction()

        } catch (e: TransactionModule.ValidationError) {

            when (e) {

                is TransactionModule.ValidationError.EmptyValue -> {

                    errorMessage.value = ""

                }

                else -> {

                    errorMessage.value = ""

                }

            }

        }

    }



    override fun sendTransaction() {

        sendDelegate?.signTransaction()

    }



    override fun onCleared() {



    }

}    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354