How do I sign and send a raw transaction using BitcoinJ?

1

Transaction transaction = new Transaction(params);
    // 遍历未花费列表,组装合适的item

    double sum = 0;
    String address = null;
    List<Unspent> unspents = new ArrayList<>();
    Map<String, AddrDTO> keysMap = new HashMap<>();
    for (Unspent utxo : unSpentBTCList) {
        /*
         * if(!script.isSentToRawPubKey() && !script.isSentToAddress()) {
         * logger.info("格式不對:" + utxo.address()); continue; }
         */
        AddrDTO addrDto = this.getAddrDTO(utxo.address());
        if (addrDto == null) {
            logger.info("address 找不到:" + utxo.address());
            continue;
        }
        keysMap.put(utxo.address(), addrDto);
        unspents.add(utxo);
        sum += utxo.amount();
        address = utxo.address();
        if (sum >= amount) {
            break;// 停止。
        }
    }
    if (sum < amount) {
        logger.error("余额不足");
        throw new RuntimeException("余额不足!");
    }

    long value = btc2Satoshi(amount);
    transaction.addOutput(Coin.valueOf(value), Address.fromBase58(params, to));
    // transaction.

    // 消费列表总金额 - 已经转账的金额 - 手续费 就等于需要返回给自己的金额了
    long longFee = btc2Satoshi(fee);
    long balance = btc2Satoshi(sum) - value - longFee;
    // 输出-转给自己
    if (balance > 0) {
        transaction.addOutput(Coin.valueOf(balance), Address.fromBase58(params, address));
    }
    int i = 0;
    for (Unspent utxo : unspents) {
        AddrDTO addrDto = keysMap.get(utxo.address());
        logger.info("xxxxxxxxxx:" + utxo.txid() + ":" + addrDto.getAddress());
        DumpedPrivateKey dumpedPrivateKey = DumpedPrivateKey.fromBase58(params, addrDto.getPrivateKey());
        Script s = new Script(Hex.decode(utxo.scriptPubKey()));
        TransactionOutPoint outPoint = new TransactionOutPoint(params, i++, Sha256Hash.wrap(utxo.txid()));
        ECKey ecKey = dumpedPrivateKey.getKey();
        transaction.addSignedInput(outPoint, s, ecKey, Transaction.SigHash.ALL, true);
        logger.info("xxxxxxxxxx:" + utxo.amount());
    }
    String hex = Hex.toHexString(transaction.bitcoinSerialize());
    logger.info("bitcoinj hex = " + hex);

Error:

Exception in thread "main" org.bitcoinj.core.ScriptException: Don't know how to sign for this kind of scriptPubKey: HASH160 PUSHDATA(20)[1a0a82f0669c14c6739e4cf1a5a3f221f657e28f] EQUAL
    at org.bitcoinj.core.Transaction.addSignedInput(Transaction.java:823)
    at com.idasex.bitcoin.BitcoinClient.signBTCTransactionData(BitcoinClient.java:337)
    at com.idasex.bitcoin.BitcoinClient.sendRawTx(BitcoinClient.java:274)
    at com.idasex.bitcoin.BitcoinClient.main(BitcoinClient.java:409)

Jon Chiang

Posted 2018-11-26T13:14:39.377

Reputation: 11

Answers

2

I'm not an expert in this field by any means (and my error message was different), but I spent last week trying to make Bitcoinj sign a transaction and send in raw form (i.e. without using transport protocol which Bitcoinj provides) and here's what I've learned (the hard way): You can't sign transactions like that. If you call tx.addSignedInput in a loop, you'll corrupt transaction's signature and it's gonna to be invalid. The solution (after brainstorming with my team and trying a few recipes here (this one didn't work), at Bitcoin Stack Exchange) was add all Inputs to the transaction and then sign them manually.

Transaction tx = new Transaction(networkParams);
tx.addOutput(Coin.valueOf(amount), targetAddress);
addInputsToTransaction(sourceAddress, tx, unspents, amount);
signInputsOfTransaction(sourceAddress, tx, key);

tx.verify();
tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
String valueToSend = byteArrayToHexString(tx.bitcoinSerialize());

and the methods:

private void addInputsToTransaction(Address sourceAddress, Transaction tx, @NonNull BalanceResponse.Unspents[] unspents, Long amount) {
    long gatheredAmount = 0L;
    long requiredAmount = amount + TX_FEE;
    for (BalanceResponse.Unspents unspent : unspents) {
        gatheredAmount += unspent.getAmount();
        TransactionOutPoint outPoint = new TransactionOutPoint(networkParams, unspent.getvOut(), Sha256Hash.wrap(unspent.getTxId()));
        TransactionInput transactionInput = new TransactionInput(networkParams, tx, hexStringToByteArray(unspent.getScriptPubKey()),
                outPoint, Coin.valueOf(unspent.getAmount());
        tx.addInput(transactionInput);

        if (gatheredAmount >= requiredAmount) {
            break;
        }
    }
    if (gatheredAmount > requiredAmount) {
        //return change to sender, in real life it should use different address
        tx.addOutput(Coin.valueOf((gatheredAmount - requiredAmount)), sourceAddress);
    }
}

private void signInputsOfTransaction(Address sourceAddress, @NonNull Transaction tx, ECKey key) {
    for (int i = 0; i < tx.getInputs().size(); i++) {
        Script scriptPubKey = ScriptBuilder.createOutputScript(sourceAddress);
        Sha256Hash hash = tx.hashForSignature(i, scriptPubKey, Transaction.SigHash.ALL, true);
        ECKey.ECDSASignature ecdsaSignature = key.sign(hash);
        TransactionSignature txSignature = new TransactionSignature(ecdsaSignature, Transaction.SigHash.ALL, true);

        if (ScriptPattern.isP2PK(scriptPubKey)) {
            tx.getInput(i).setScriptSig(ScriptBuilder.createInputScript(txSignature));
        } else {
            if (!ScriptPattern.isP2PKH(scriptPubKey)) {
                throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unable to sign this scrptPubKey: " + scriptPubKey);
            }
            tx.getInput(i).setScriptSig(ScriptBuilder.createInputScript(txSignature, key));
        }
    }
}

I hope, it will help you.

Oleg Kuznetsov

Posted 2018-11-26T13:14:39.377

Reputation: 21