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);
Erro:
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)
Resposta
Não sou um especialista neste campo de forma alguma (e minha mensagem de erro foi diferente), mas passei a semana passada tentando fazer O Bitcoinj assina uma transação e envia em formato bruto (ou seja, sem usar o protocolo de transporte que o Bitcoinj fornece) e aqui está o que aprendi (da maneira mais difícil): Você não pode assinar transações como essa. Se você chamar tx.addSignedInput
em um loop, você “corromperá a assinatura da transação e ela ficará inválida . A solução (depois de fazer um brainstorming com minha equipe e tentar algumas receitas aqui ( este não funcionou), no Bitcoin Stack Exchange) foi adicione todas as entradas à transação e, em seguida, assine-as manualmente .
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());
e os métodos:
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)); } } }
Espero que ajude você.
Comentários
- Alterar verdadeiro para falso, funciona para mim. 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);