2
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)

1 Answer 1

5

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.

1
  • Change true to false, it works for me. Sha256Hash hash = tx.hashForSignature(i, scriptPubKey, Transaction.SigHash.ALL, false); ECKey.ECDSASignature ecdsaSignature = key.sign(hash); TransactionSignature txSignature = new TransactionSignature(ecdsaSignature, Transaction.SigHash.ALL, false);
    – tiennv
    Commented Dec 19, 2020 at 20:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.