@Test public void testSignMultiUTXOSegWitTransaction() { ArrayList<BitcoinTransaction.UTXO> utxos = new ArrayList<>(); utxos.add(new BitcoinTransaction.UTXO("ea2cdabdb11f2afdbe9e9d51744d5924bb3917ae4b383b3ef7c9c3dbb691653a", 1, 100000000, "2NARMf1Wb3rhiYhGBwYuCgKEDi4zmojTsvk", "a914bc64b2d79807cd3d72101c3298b89117d32097fb87", "")); utxos.add(new BitcoinTransaction.UTXO("ad3b68e534f6deb12e1b8c1e1098b76e4b29c0e60416daae90487a91a982e366", 0, 100000000, "2NARMf1Wb3rhiYhGBwYuCgKEDi4zmojTsvk", "a914bc64b2d79807cd3d72101c3298b89117d32097fb87", "")); BitcoinTransaction transaction = new BitcoinTransaction("mvqN876ymCo7HbRbmYoaoMfwigBdEKx4J1", 0, 195000000, 210090, utxos); walletMetadata.setSource(Metadata.FROM_WIF); walletMetadata.setNetwork(Network.TESTNET); walletMetadata.setSegWit(Metadata.P2WPKH); Wallet wallet = WalletManager.importWalletFromPrivateKey(walletMetadata, "cT4fTJyLd5RmSZFHnkGmVCzXDKuJLbyTt7cy77ghTTCagzNdPH1j", SampleKey.PASSWORD, true); TxSignResult signResult = transaction.signSegWitTransaction("0", SampleKey.PASSWORD, wallet); String expectedSignedHex = "020000000001023a6591b6dbc3c9f73e3b384bae1739bb24594d74519d9ebefd2a1fb1bdda2cea0100000017160014e6cfaab9a59ba187f0a45db0b169c21bb48f09b3ffffffff66e382a9917a4890aeda1604e6c0294b6eb798101e8c1b2eb1def634e5683bad0000000017160014e6cfaab9a59ba187f0a45db0b169c21bb48f09b3ffffffff02c0769f0b000000001976a914a80543dc9a417df6cccd36d1c1d85b04a8a4f49f88ac961649000000000017a914bc64b2d79807cd3d72101c3298b89117d32097fb870247304402204dfe8a3b8d22d7ebf762067ea4696b660c6550c92121ee11d582887b4c66e84302200d2945733954ff9f5edc259181f25206fcda79b04f5d453f7f536755dd6bb39d012102506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba02483045022100d6d1d9fa05f40d215554a0ca15642aca73e4f3edf47f7fc8edc52f80289d9dd40220162a53822d0a6913c22b27ffe60543d7b8ec2ff7943ce6f15a56f874daa33c89012102506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba00000000"; Assert.assertEquals(expectedSignedHex, signResult.getSignedTx()); Assert.assertEquals("d25fa6a70404e9ad051a2ef12128e02736668736dbab9d427d132e61c551f5a9", signResult.getTxHash()); Assert.assertEquals("cb875cbaabe98e37f179b813a567350dae47cbd4770c5f499cf32869ca6d070d", signResult.getWtxID()); }
public TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet) { collectPrvKeysAndAddress(Metadata.P2WPKH, password, wallet); long totalAmount = 0L; boolean hasChange = false; for (UTXO output : getOutputs()) { totalAmount += output.getAmount(); } if (totalAmount < getAmount()) { throw new TokenException(Messages.INSUFFICIENT_FUNDS); } long changeAmount = totalAmount - (getAmount() + getFee()); Address toAddress = Address.fromBase58(network, to); byte[] targetScriptPubKey; if (toAddress.isP2SHAddress()) { targetScriptPubKey = ScriptBuilder.createP2SHOutputScript(toAddress.getHash160()).getProgram(); } else { targetScriptPubKey = ScriptBuilder.createOutputScript(toAddress).getProgram(); } byte[] changeScriptPubKey = ScriptBuilder.createP2SHOutputScript(changeAddress.getHash160()).getProgram(); byte[] hashPrevouts; byte[] hashOutputs; byte[] hashSequence; try { UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { TransactionOutPoint outPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); outPoint.bitcoinSerialize(stream); } hashPrevouts = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); TransactionOutput targetOutput = new TransactionOutput(this.network, null, Coin.valueOf(amount), toAddress); targetOutput.bitcoinSerialize(stream); if (changeAmount >= DUST_THRESHOLD) { hasChange = true; TransactionOutput changeOutput = new TransactionOutput(this.network, null, Coin.valueOf(changeAmount), changeAddress); changeOutput.bitcoinSerialize(stream); } hashOutputs = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } hashSequence = Sha256Hash.hashTwice(stream.toByteArray()); List<byte[]> witnesses = new ArrayList<>(); List<String> redeemScripts = new ArrayList<>(); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey key = ECKey.fromPrivate(prvKey, true); String redeemScript = String.format("0014%s", NumericUtil.bytesToHex(key.getPubKeyHash())); redeemScripts.add(redeemScript); stream = new UnsafeByteArrayOutputStream(); TransactionOutPoint txOutPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); txOutPoint.bitcoinSerialize(stream); byte[] outpoint = stream.toByteArray(); byte[] scriptCode = NumericUtil.hexToBytes(String.format("0x1976a914%s88ac", NumericUtil.bytesToHex(key.getPubKeyHash()))); stream = new UnsafeByteArrayOutputStream(); Utils.uint32ToByteStreamLE(2L, stream); stream.write(hashPrevouts); stream.write(hashSequence); stream.write(outpoint); stream.write(scriptCode); Utils.uint64ToByteStreamLE(BigInteger.valueOf(utxo.getAmount()), stream); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); stream.write(hashOutputs); Utils.uint32ToByteStreamLE(locktime, stream); Utils.uint32ToByteStreamLE(1L, stream); byte[] hashPreimage = stream.toByteArray(); byte[] sigHash = Sha256Hash.hashTwice(hashPreimage); ECKey.ECDSASignature signature = key.sign(Sha256Hash.wrap(sigHash)); byte hashType = 0x01; byte[] sig = ByteUtil.concat(signature.encodeToDER(), new byte[]{hashType}); witnesses.add(sig); } UnsafeByteArrayOutputStream[] serialStreams = new UnsafeByteArrayOutputStream[]{ new UnsafeByteArrayOutputStream(), new UnsafeByteArrayOutputStream() }; for (int idx = 0; idx < 2; idx++) { stream = serialStreams[idx]; Utils.uint32ToByteStreamLE(2L, stream); if (idx == 0) { stream.write(0x00); stream.write(0x01); } stream.write(new VarInt(getOutputs().size()).encode()); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); stream.write(NumericUtil.reverseBytes(NumericUtil.hexToBytes(utxo.txHash))); Utils.uint32ToByteStreamLE(utxo.getVout(), stream); stream.write(0x17); stream.write(0x16); stream.write(NumericUtil.hexToBytes(redeemScripts.get(i))); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } int outputSize = hasChange ? 2 : 1; stream.write(new VarInt(outputSize).encode()); Utils.uint64ToByteStreamLE(BigInteger.valueOf(amount), stream); stream.write(new VarInt(targetScriptPubKey.length).encode()); stream.write(targetScriptPubKey); if (hasChange) { Utils.uint64ToByteStreamLE(BigInteger.valueOf(changeAmount), stream); stream.write(new VarInt(changeScriptPubKey.length).encode()); stream.write(changeScriptPubKey); } if (idx == 0) { for (int i = 0; i < witnesses.size(); i++) { BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey ecKey = ECKey.fromPrivate(prvKey); byte[] wit = witnesses.get(i); stream.write(new VarInt(2).encode()); stream.write(new VarInt(wit.length).encode()); stream.write(wit); stream.write(new VarInt(ecKey.getPubKey().length).encode()); stream.write(ecKey.getPubKey()); } } Utils.uint32ToByteStreamLE(locktime, stream); } byte[] signed = serialStreams[0].toByteArray(); String signedHex = NumericUtil.bytesToHex(signed); String wtxID = NumericUtil.bytesToHex(Sha256Hash.hashTwice(signed)); wtxID = NumericUtil.beBigEndianHex(wtxID); String txHash = NumericUtil.bytesToHex(Sha256Hash.hashTwice(serialStreams[1].toByteArray())); txHash = NumericUtil.beBigEndianHex(txHash); return new TxSignResult(signedHex, txHash, wtxID); } catch (IOException ex) { throw new TokenException("OutputStream error"); } }
BitcoinTransaction implements TransactionSigner { public TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet) { collectPrvKeysAndAddress(Metadata.P2WPKH, password, wallet); long totalAmount = 0L; boolean hasChange = false; for (UTXO output : getOutputs()) { totalAmount += output.getAmount(); } if (totalAmount < getAmount()) { throw new TokenException(Messages.INSUFFICIENT_FUNDS); } long changeAmount = totalAmount - (getAmount() + getFee()); Address toAddress = Address.fromBase58(network, to); byte[] targetScriptPubKey; if (toAddress.isP2SHAddress()) { targetScriptPubKey = ScriptBuilder.createP2SHOutputScript(toAddress.getHash160()).getProgram(); } else { targetScriptPubKey = ScriptBuilder.createOutputScript(toAddress).getProgram(); } byte[] changeScriptPubKey = ScriptBuilder.createP2SHOutputScript(changeAddress.getHash160()).getProgram(); byte[] hashPrevouts; byte[] hashOutputs; byte[] hashSequence; try { UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { TransactionOutPoint outPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); outPoint.bitcoinSerialize(stream); } hashPrevouts = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); TransactionOutput targetOutput = new TransactionOutput(this.network, null, Coin.valueOf(amount), toAddress); targetOutput.bitcoinSerialize(stream); if (changeAmount >= DUST_THRESHOLD) { hasChange = true; TransactionOutput changeOutput = new TransactionOutput(this.network, null, Coin.valueOf(changeAmount), changeAddress); changeOutput.bitcoinSerialize(stream); } hashOutputs = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } hashSequence = Sha256Hash.hashTwice(stream.toByteArray()); List<byte[]> witnesses = new ArrayList<>(); List<String> redeemScripts = new ArrayList<>(); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey key = ECKey.fromPrivate(prvKey, true); String redeemScript = String.format("0014%s", NumericUtil.bytesToHex(key.getPubKeyHash())); redeemScripts.add(redeemScript); stream = new UnsafeByteArrayOutputStream(); TransactionOutPoint txOutPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); txOutPoint.bitcoinSerialize(stream); byte[] outpoint = stream.toByteArray(); byte[] scriptCode = NumericUtil.hexToBytes(String.format("0x1976a914%s88ac", NumericUtil.bytesToHex(key.getPubKeyHash()))); stream = new UnsafeByteArrayOutputStream(); Utils.uint32ToByteStreamLE(2L, stream); stream.write(hashPrevouts); stream.write(hashSequence); stream.write(outpoint); stream.write(scriptCode); Utils.uint64ToByteStreamLE(BigInteger.valueOf(utxo.getAmount()), stream); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); stream.write(hashOutputs); Utils.uint32ToByteStreamLE(locktime, stream); Utils.uint32ToByteStreamLE(1L, stream); byte[] hashPreimage = stream.toByteArray(); byte[] sigHash = Sha256Hash.hashTwice(hashPreimage); ECKey.ECDSASignature signature = key.sign(Sha256Hash.wrap(sigHash)); byte hashType = 0x01; byte[] sig = ByteUtil.concat(signature.encodeToDER(), new byte[]{hashType}); witnesses.add(sig); } UnsafeByteArrayOutputStream[] serialStreams = new UnsafeByteArrayOutputStream[]{ new UnsafeByteArrayOutputStream(), new UnsafeByteArrayOutputStream() }; for (int idx = 0; idx < 2; idx++) { stream = serialStreams[idx]; Utils.uint32ToByteStreamLE(2L, stream); if (idx == 0) { stream.write(0x00); stream.write(0x01); } stream.write(new VarInt(getOutputs().size()).encode()); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); stream.write(NumericUtil.reverseBytes(NumericUtil.hexToBytes(utxo.txHash))); Utils.uint32ToByteStreamLE(utxo.getVout(), stream); stream.write(0x17); stream.write(0x16); stream.write(NumericUtil.hexToBytes(redeemScripts.get(i))); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } int outputSize = hasChange ? 2 : 1; stream.write(new VarInt(outputSize).encode()); Utils.uint64ToByteStreamLE(BigInteger.valueOf(amount), stream); stream.write(new VarInt(targetScriptPubKey.length).encode()); stream.write(targetScriptPubKey); if (hasChange) { Utils.uint64ToByteStreamLE(BigInteger.valueOf(changeAmount), stream); stream.write(new VarInt(changeScriptPubKey.length).encode()); stream.write(changeScriptPubKey); } if (idx == 0) { for (int i = 0; i < witnesses.size(); i++) { BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey ecKey = ECKey.fromPrivate(prvKey); byte[] wit = witnesses.get(i); stream.write(new VarInt(2).encode()); stream.write(new VarInt(wit.length).encode()); stream.write(wit); stream.write(new VarInt(ecKey.getPubKey().length).encode()); stream.write(ecKey.getPubKey()); } } Utils.uint32ToByteStreamLE(locktime, stream); } byte[] signed = serialStreams[0].toByteArray(); String signedHex = NumericUtil.bytesToHex(signed); String wtxID = NumericUtil.bytesToHex(Sha256Hash.hashTwice(signed)); wtxID = NumericUtil.beBigEndianHex(wtxID); String txHash = NumericUtil.bytesToHex(Sha256Hash.hashTwice(serialStreams[1].toByteArray())); txHash = NumericUtil.beBigEndianHex(txHash); return new TxSignResult(signedHex, txHash, wtxID); } catch (IOException ex) { throw new TokenException("OutputStream error"); } } }
BitcoinTransaction implements TransactionSigner { public TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet) { collectPrvKeysAndAddress(Metadata.P2WPKH, password, wallet); long totalAmount = 0L; boolean hasChange = false; for (UTXO output : getOutputs()) { totalAmount += output.getAmount(); } if (totalAmount < getAmount()) { throw new TokenException(Messages.INSUFFICIENT_FUNDS); } long changeAmount = totalAmount - (getAmount() + getFee()); Address toAddress = Address.fromBase58(network, to); byte[] targetScriptPubKey; if (toAddress.isP2SHAddress()) { targetScriptPubKey = ScriptBuilder.createP2SHOutputScript(toAddress.getHash160()).getProgram(); } else { targetScriptPubKey = ScriptBuilder.createOutputScript(toAddress).getProgram(); } byte[] changeScriptPubKey = ScriptBuilder.createP2SHOutputScript(changeAddress.getHash160()).getProgram(); byte[] hashPrevouts; byte[] hashOutputs; byte[] hashSequence; try { UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { TransactionOutPoint outPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); outPoint.bitcoinSerialize(stream); } hashPrevouts = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); TransactionOutput targetOutput = new TransactionOutput(this.network, null, Coin.valueOf(amount), toAddress); targetOutput.bitcoinSerialize(stream); if (changeAmount >= DUST_THRESHOLD) { hasChange = true; TransactionOutput changeOutput = new TransactionOutput(this.network, null, Coin.valueOf(changeAmount), changeAddress); changeOutput.bitcoinSerialize(stream); } hashOutputs = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } hashSequence = Sha256Hash.hashTwice(stream.toByteArray()); List<byte[]> witnesses = new ArrayList<>(); List<String> redeemScripts = new ArrayList<>(); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey key = ECKey.fromPrivate(prvKey, true); String redeemScript = String.format("0014%s", NumericUtil.bytesToHex(key.getPubKeyHash())); redeemScripts.add(redeemScript); stream = new UnsafeByteArrayOutputStream(); TransactionOutPoint txOutPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); txOutPoint.bitcoinSerialize(stream); byte[] outpoint = stream.toByteArray(); byte[] scriptCode = NumericUtil.hexToBytes(String.format("0x1976a914%s88ac", NumericUtil.bytesToHex(key.getPubKeyHash()))); stream = new UnsafeByteArrayOutputStream(); Utils.uint32ToByteStreamLE(2L, stream); stream.write(hashPrevouts); stream.write(hashSequence); stream.write(outpoint); stream.write(scriptCode); Utils.uint64ToByteStreamLE(BigInteger.valueOf(utxo.getAmount()), stream); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); stream.write(hashOutputs); Utils.uint32ToByteStreamLE(locktime, stream); Utils.uint32ToByteStreamLE(1L, stream); byte[] hashPreimage = stream.toByteArray(); byte[] sigHash = Sha256Hash.hashTwice(hashPreimage); ECKey.ECDSASignature signature = key.sign(Sha256Hash.wrap(sigHash)); byte hashType = 0x01; byte[] sig = ByteUtil.concat(signature.encodeToDER(), new byte[]{hashType}); witnesses.add(sig); } UnsafeByteArrayOutputStream[] serialStreams = new UnsafeByteArrayOutputStream[]{ new UnsafeByteArrayOutputStream(), new UnsafeByteArrayOutputStream() }; for (int idx = 0; idx < 2; idx++) { stream = serialStreams[idx]; Utils.uint32ToByteStreamLE(2L, stream); if (idx == 0) { stream.write(0x00); stream.write(0x01); } stream.write(new VarInt(getOutputs().size()).encode()); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); stream.write(NumericUtil.reverseBytes(NumericUtil.hexToBytes(utxo.txHash))); Utils.uint32ToByteStreamLE(utxo.getVout(), stream); stream.write(0x17); stream.write(0x16); stream.write(NumericUtil.hexToBytes(redeemScripts.get(i))); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } int outputSize = hasChange ? 2 : 1; stream.write(new VarInt(outputSize).encode()); Utils.uint64ToByteStreamLE(BigInteger.valueOf(amount), stream); stream.write(new VarInt(targetScriptPubKey.length).encode()); stream.write(targetScriptPubKey); if (hasChange) { Utils.uint64ToByteStreamLE(BigInteger.valueOf(changeAmount), stream); stream.write(new VarInt(changeScriptPubKey.length).encode()); stream.write(changeScriptPubKey); } if (idx == 0) { for (int i = 0; i < witnesses.size(); i++) { BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey ecKey = ECKey.fromPrivate(prvKey); byte[] wit = witnesses.get(i); stream.write(new VarInt(2).encode()); stream.write(new VarInt(wit.length).encode()); stream.write(wit); stream.write(new VarInt(ecKey.getPubKey().length).encode()); stream.write(ecKey.getPubKey()); } } Utils.uint32ToByteStreamLE(locktime, stream); } byte[] signed = serialStreams[0].toByteArray(); String signedHex = NumericUtil.bytesToHex(signed); String wtxID = NumericUtil.bytesToHex(Sha256Hash.hashTwice(signed)); wtxID = NumericUtil.beBigEndianHex(wtxID); String txHash = NumericUtil.bytesToHex(Sha256Hash.hashTwice(serialStreams[1].toByteArray())); txHash = NumericUtil.beBigEndianHex(txHash); return new TxSignResult(signedHex, txHash, wtxID); } catch (IOException ex) { throw new TokenException("OutputStream error"); } } BitcoinTransaction(String to, int changeIdx, long amount, long fee, ArrayList<UTXO> outputs); }
BitcoinTransaction implements TransactionSigner { public TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet) { collectPrvKeysAndAddress(Metadata.P2WPKH, password, wallet); long totalAmount = 0L; boolean hasChange = false; for (UTXO output : getOutputs()) { totalAmount += output.getAmount(); } if (totalAmount < getAmount()) { throw new TokenException(Messages.INSUFFICIENT_FUNDS); } long changeAmount = totalAmount - (getAmount() + getFee()); Address toAddress = Address.fromBase58(network, to); byte[] targetScriptPubKey; if (toAddress.isP2SHAddress()) { targetScriptPubKey = ScriptBuilder.createP2SHOutputScript(toAddress.getHash160()).getProgram(); } else { targetScriptPubKey = ScriptBuilder.createOutputScript(toAddress).getProgram(); } byte[] changeScriptPubKey = ScriptBuilder.createP2SHOutputScript(changeAddress.getHash160()).getProgram(); byte[] hashPrevouts; byte[] hashOutputs; byte[] hashSequence; try { UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { TransactionOutPoint outPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); outPoint.bitcoinSerialize(stream); } hashPrevouts = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); TransactionOutput targetOutput = new TransactionOutput(this.network, null, Coin.valueOf(amount), toAddress); targetOutput.bitcoinSerialize(stream); if (changeAmount >= DUST_THRESHOLD) { hasChange = true; TransactionOutput changeOutput = new TransactionOutput(this.network, null, Coin.valueOf(changeAmount), changeAddress); changeOutput.bitcoinSerialize(stream); } hashOutputs = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } hashSequence = Sha256Hash.hashTwice(stream.toByteArray()); List<byte[]> witnesses = new ArrayList<>(); List<String> redeemScripts = new ArrayList<>(); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey key = ECKey.fromPrivate(prvKey, true); String redeemScript = String.format("0014%s", NumericUtil.bytesToHex(key.getPubKeyHash())); redeemScripts.add(redeemScript); stream = new UnsafeByteArrayOutputStream(); TransactionOutPoint txOutPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); txOutPoint.bitcoinSerialize(stream); byte[] outpoint = stream.toByteArray(); byte[] scriptCode = NumericUtil.hexToBytes(String.format("0x1976a914%s88ac", NumericUtil.bytesToHex(key.getPubKeyHash()))); stream = new UnsafeByteArrayOutputStream(); Utils.uint32ToByteStreamLE(2L, stream); stream.write(hashPrevouts); stream.write(hashSequence); stream.write(outpoint); stream.write(scriptCode); Utils.uint64ToByteStreamLE(BigInteger.valueOf(utxo.getAmount()), stream); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); stream.write(hashOutputs); Utils.uint32ToByteStreamLE(locktime, stream); Utils.uint32ToByteStreamLE(1L, stream); byte[] hashPreimage = stream.toByteArray(); byte[] sigHash = Sha256Hash.hashTwice(hashPreimage); ECKey.ECDSASignature signature = key.sign(Sha256Hash.wrap(sigHash)); byte hashType = 0x01; byte[] sig = ByteUtil.concat(signature.encodeToDER(), new byte[]{hashType}); witnesses.add(sig); } UnsafeByteArrayOutputStream[] serialStreams = new UnsafeByteArrayOutputStream[]{ new UnsafeByteArrayOutputStream(), new UnsafeByteArrayOutputStream() }; for (int idx = 0; idx < 2; idx++) { stream = serialStreams[idx]; Utils.uint32ToByteStreamLE(2L, stream); if (idx == 0) { stream.write(0x00); stream.write(0x01); } stream.write(new VarInt(getOutputs().size()).encode()); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); stream.write(NumericUtil.reverseBytes(NumericUtil.hexToBytes(utxo.txHash))); Utils.uint32ToByteStreamLE(utxo.getVout(), stream); stream.write(0x17); stream.write(0x16); stream.write(NumericUtil.hexToBytes(redeemScripts.get(i))); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } int outputSize = hasChange ? 2 : 1; stream.write(new VarInt(outputSize).encode()); Utils.uint64ToByteStreamLE(BigInteger.valueOf(amount), stream); stream.write(new VarInt(targetScriptPubKey.length).encode()); stream.write(targetScriptPubKey); if (hasChange) { Utils.uint64ToByteStreamLE(BigInteger.valueOf(changeAmount), stream); stream.write(new VarInt(changeScriptPubKey.length).encode()); stream.write(changeScriptPubKey); } if (idx == 0) { for (int i = 0; i < witnesses.size(); i++) { BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey ecKey = ECKey.fromPrivate(prvKey); byte[] wit = witnesses.get(i); stream.write(new VarInt(2).encode()); stream.write(new VarInt(wit.length).encode()); stream.write(wit); stream.write(new VarInt(ecKey.getPubKey().length).encode()); stream.write(ecKey.getPubKey()); } } Utils.uint32ToByteStreamLE(locktime, stream); } byte[] signed = serialStreams[0].toByteArray(); String signedHex = NumericUtil.bytesToHex(signed); String wtxID = NumericUtil.bytesToHex(Sha256Hash.hashTwice(signed)); wtxID = NumericUtil.beBigEndianHex(wtxID); String txHash = NumericUtil.bytesToHex(Sha256Hash.hashTwice(serialStreams[1].toByteArray())); txHash = NumericUtil.beBigEndianHex(txHash); return new TxSignResult(signedHex, txHash, wtxID); } catch (IOException ex) { throw new TokenException("OutputStream error"); } } BitcoinTransaction(String to, int changeIdx, long amount, long fee, ArrayList<UTXO> outputs); @Override String toString(); String getTo(); void setTo(String to); long getAmount(); void setAmount(long amount); List<UTXO> getOutputs(); void setOutputs(List<UTXO> outputs); String getMemo(); void setMemo(String memo); long getFee(); void setFee(long fee); int getChangeIdx(); void setChangeIdx(int changeIdx); TxSignResult signTransaction(String chainID, String password, Wallet wallet); TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet); }
BitcoinTransaction implements TransactionSigner { public TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet) { collectPrvKeysAndAddress(Metadata.P2WPKH, password, wallet); long totalAmount = 0L; boolean hasChange = false; for (UTXO output : getOutputs()) { totalAmount += output.getAmount(); } if (totalAmount < getAmount()) { throw new TokenException(Messages.INSUFFICIENT_FUNDS); } long changeAmount = totalAmount - (getAmount() + getFee()); Address toAddress = Address.fromBase58(network, to); byte[] targetScriptPubKey; if (toAddress.isP2SHAddress()) { targetScriptPubKey = ScriptBuilder.createP2SHOutputScript(toAddress.getHash160()).getProgram(); } else { targetScriptPubKey = ScriptBuilder.createOutputScript(toAddress).getProgram(); } byte[] changeScriptPubKey = ScriptBuilder.createP2SHOutputScript(changeAddress.getHash160()).getProgram(); byte[] hashPrevouts; byte[] hashOutputs; byte[] hashSequence; try { UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { TransactionOutPoint outPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); outPoint.bitcoinSerialize(stream); } hashPrevouts = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); TransactionOutput targetOutput = new TransactionOutput(this.network, null, Coin.valueOf(amount), toAddress); targetOutput.bitcoinSerialize(stream); if (changeAmount >= DUST_THRESHOLD) { hasChange = true; TransactionOutput changeOutput = new TransactionOutput(this.network, null, Coin.valueOf(changeAmount), changeAddress); changeOutput.bitcoinSerialize(stream); } hashOutputs = Sha256Hash.hashTwice(stream.toByteArray()); stream = new UnsafeByteArrayOutputStream(); for (UTXO utxo : getOutputs()) { Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } hashSequence = Sha256Hash.hashTwice(stream.toByteArray()); List<byte[]> witnesses = new ArrayList<>(); List<String> redeemScripts = new ArrayList<>(); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey key = ECKey.fromPrivate(prvKey, true); String redeemScript = String.format("0014%s", NumericUtil.bytesToHex(key.getPubKeyHash())); redeemScripts.add(redeemScript); stream = new UnsafeByteArrayOutputStream(); TransactionOutPoint txOutPoint = new TransactionOutPoint(this.network, utxo.vout, Sha256Hash.wrap(utxo.txHash)); txOutPoint.bitcoinSerialize(stream); byte[] outpoint = stream.toByteArray(); byte[] scriptCode = NumericUtil.hexToBytes(String.format("0x1976a914%s88ac", NumericUtil.bytesToHex(key.getPubKeyHash()))); stream = new UnsafeByteArrayOutputStream(); Utils.uint32ToByteStreamLE(2L, stream); stream.write(hashPrevouts); stream.write(hashSequence); stream.write(outpoint); stream.write(scriptCode); Utils.uint64ToByteStreamLE(BigInteger.valueOf(utxo.getAmount()), stream); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); stream.write(hashOutputs); Utils.uint32ToByteStreamLE(locktime, stream); Utils.uint32ToByteStreamLE(1L, stream); byte[] hashPreimage = stream.toByteArray(); byte[] sigHash = Sha256Hash.hashTwice(hashPreimage); ECKey.ECDSASignature signature = key.sign(Sha256Hash.wrap(sigHash)); byte hashType = 0x01; byte[] sig = ByteUtil.concat(signature.encodeToDER(), new byte[]{hashType}); witnesses.add(sig); } UnsafeByteArrayOutputStream[] serialStreams = new UnsafeByteArrayOutputStream[]{ new UnsafeByteArrayOutputStream(), new UnsafeByteArrayOutputStream() }; for (int idx = 0; idx < 2; idx++) { stream = serialStreams[idx]; Utils.uint32ToByteStreamLE(2L, stream); if (idx == 0) { stream.write(0x00); stream.write(0x01); } stream.write(new VarInt(getOutputs().size()).encode()); for (int i = 0; i < getOutputs().size(); i++) { UTXO utxo = getOutputs().get(i); stream.write(NumericUtil.reverseBytes(NumericUtil.hexToBytes(utxo.txHash))); Utils.uint32ToByteStreamLE(utxo.getVout(), stream); stream.write(0x17); stream.write(0x16); stream.write(NumericUtil.hexToBytes(redeemScripts.get(i))); Utils.uint32ToByteStreamLE(utxo.getSequence(), stream); } int outputSize = hasChange ? 2 : 1; stream.write(new VarInt(outputSize).encode()); Utils.uint64ToByteStreamLE(BigInteger.valueOf(amount), stream); stream.write(new VarInt(targetScriptPubKey.length).encode()); stream.write(targetScriptPubKey); if (hasChange) { Utils.uint64ToByteStreamLE(BigInteger.valueOf(changeAmount), stream); stream.write(new VarInt(changeScriptPubKey.length).encode()); stream.write(changeScriptPubKey); } if (idx == 0) { for (int i = 0; i < witnesses.size(); i++) { BigInteger prvKey = Metadata.FROM_WIF.equals(wallet.getMetadata().getSource()) ? prvKeys.get(0) : prvKeys.get(i); ECKey ecKey = ECKey.fromPrivate(prvKey); byte[] wit = witnesses.get(i); stream.write(new VarInt(2).encode()); stream.write(new VarInt(wit.length).encode()); stream.write(wit); stream.write(new VarInt(ecKey.getPubKey().length).encode()); stream.write(ecKey.getPubKey()); } } Utils.uint32ToByteStreamLE(locktime, stream); } byte[] signed = serialStreams[0].toByteArray(); String signedHex = NumericUtil.bytesToHex(signed); String wtxID = NumericUtil.bytesToHex(Sha256Hash.hashTwice(signed)); wtxID = NumericUtil.beBigEndianHex(wtxID); String txHash = NumericUtil.bytesToHex(Sha256Hash.hashTwice(serialStreams[1].toByteArray())); txHash = NumericUtil.beBigEndianHex(txHash); return new TxSignResult(signedHex, txHash, wtxID); } catch (IOException ex) { throw new TokenException("OutputStream error"); } } BitcoinTransaction(String to, int changeIdx, long amount, long fee, ArrayList<UTXO> outputs); @Override String toString(); String getTo(); void setTo(String to); long getAmount(); void setAmount(long amount); List<UTXO> getOutputs(); void setOutputs(List<UTXO> outputs); String getMemo(); void setMemo(String memo); long getFee(); void setFee(long fee); int getChangeIdx(); void setChangeIdx(int changeIdx); TxSignResult signTransaction(String chainID, String password, Wallet wallet); TxSignResult signSegWitTransaction(String chainId, String password, Wallet wallet);  }