@Test public void bitcoinSegwitSign() { ArrayList<BitcoinTransaction.UTXO> utxos = new ArrayList<>(); utxos.add(new BitcoinTransaction.UTXO("c2ceb5088cf39b677705526065667a3992c68cc18593a9af12607e057672717f", 0, 50000, "2MwN441dq8qudMvtM5eLVwC3u4zfKuGSQAB", "a9142d2b1ef5ee4cf6c3ebc8cf66a602783798f7875987", "0/0")); utxos.add(new BitcoinTransaction.UTXO("9ad628d450952a575af59f7d416c9bc337d184024608f1d2e13383c44bd5cd74", 0, 50000, "2N54wJxopnWTvBfqgAPVWqXVEdaqoH7Suvf", "a91481af6d803fdc6dca1f3a1d03f5ffe8124cd1b44787", "0/1")); walletMetadata.setSource(Metadata.FROM_MNEMONIC); walletMetadata.setNetwork(Network.TESTNET); walletMetadata.setSegWit(Metadata.P2WPKH); Wallet wallet = WalletManager.importWalletFromMnemonic(walletMetadata, SampleKey.MNEMONIC, BIP44Util.BITCOIN_SEGWIT_TESTNET_PATH, SampleKey.PASSWORD, true); BitcoinTransaction transaction; TxSignResult signResult; try { transaction = new BitcoinTransaction("2N9wBy6f1KTUF5h2UUeqRdKnBT6oSMh4Whp", 0, 2000, 10000, utxos); Assert.fail("Should throw amount_less_than_minimum"); } catch (TokenException ex) { Assert.assertEquals(Messages.AMOUNT_LESS_THAN_MINIMUM, ex.getMessage()); } transaction = new BitcoinTransaction("2N9wBy6f1KTUF5h2UUeqRdKnBT6oSMh4Whp", 0, 88000, 10000, utxos); signResult = transaction.signSegWitTransaction("0", SampleKey.PASSWORD, wallet); String expectedSignedHex = "020000000001027f717276057e6012afa99385c18cc692397a666560520577679bf38c08b5cec20000000017160014654fbb08267f3d50d715a8f1abb55979b160dd5bffffffff74cdd54bc48333e1d2f108460284d137c39b6c417d9ff55a572a9550d428d69a00000000171600149d66aa6399de69d5c5ae19f9098047760251a854ffffffff01c05701000000000017a914b710f6e5049eaf0404c2f02f091dd5bb79fa135e870247304402205fd9dea5df0db5cc7b1d4b969f63b4526fb00fd5563ab91012cb511744a53d570220784abfe099a2b063b1cfc1f145fef2ffcb100b0891514fa164d357f0ef7ca6bb0121031aee5e20399d68cf0035d1a21564868f22bc448ab205292b4279136b15ecaebc02483045022100b0246c12428dbf863fcc9060ab6fc46dc2135adaa6cf8117de49f9acecaccf6c022059377d05c9cab24b7dec14242ea3206cc1f464d5ff9904dca515fc71766507cd012103a241c8d13dd5c92475652c43bf56580fbf9f1e8bc0aa0132ddc8443c03062bb900000000"; Assert.assertEquals(expectedSignedHex, signResult.getSignedTx()); transaction = new BitcoinTransaction("2N9wBy6f1KTUF5h2UUeqRdKnBT6oSMh4Whp", 0, 80000, 10000, utxos); signResult = transaction.signSegWitTransaction("0", SampleKey.PASSWORD, wallet); expectedSignedHex = "020000000001027f717276057e6012afa99385c18cc692397a666560520577679bf38c08b5cec20000000017160014654fbb08267f3d50d715a8f1abb55979b160dd5bffffffff74cdd54bc48333e1d2f108460284d137c39b6c417d9ff55a572a9550d428d69a00000000171600149d66aa6399de69d5c5ae19f9098047760251a854ffffffff02803801000000000017a914b710f6e5049eaf0404c2f02f091dd5bb79fa135e87102700000000000017a914755fba51b5c443b9f16b1f86665dec10dd7a25c58702483045022100f0c66cd322e50f992ad34448fb3bf823066e5ffaa8e840a901058a863a4d950c02206cdafb1ad1ef4d938122b106069d8b435387e4d55711f50a46a8d91d9f674c550121031aee5e20399d68cf0035d1a21564868f22bc448ab205292b4279136b15ecaebc02483045022100cfe92e4ad4fbfc13be20afc6f37429e26426257d015b409d28c260544e581b2c022028412816d1fef11093b474c2c662a25a4062f4e37d06ce66207863de98814a07012103a241c8d13dd5c92475652c43bf56580fbf9f1e8bc0aa0132ddc8443c03062bb900000000"; Assert.assertEquals(expectedSignedHex, signResult.getSignedTx()); transaction = new BitcoinTransaction("moLK3tBG86ifpDDTqAQzs4a9cUoNjVLRE3", 0, 80000, 10000, utxos); walletMetadata.setSource(Metadata.FROM_WIF); walletMetadata.setNetwork(Network.TESTNET); walletMetadata.setSegWit(Metadata.P2WPKH); wallet = WalletManager.importWalletFromPrivateKey(walletMetadata, "cT4fTJyLd5RmSZFHnkGmVCzXDKuJLbyTt7cy77ghTTCagzNdPH1j", SampleKey.PASSWORD, true); signResult = transaction.signSegWitTransaction("0", SampleKey.PASSWORD, wallet); expectedSignedHex = "020000000001027f717276057e6012afa99385c18cc692397a666560520577679bf38c08b5cec20000000017160014e6cfaab9a59ba187f0a45db0b169c21bb48f09b3ffffffff74cdd54bc48333e1d2f108460284d137c39b6c417d9ff55a572a9550d428d69a0000000017160014e6cfaab9a59ba187f0a45db0b169c21bb48f09b3ffffffff0280380100000000001976a91455bdc1b42e3bed851959846ddf600e96125423e088ac102700000000000017a914bc64b2d79807cd3d72101c3298b89117d32097fb8702483045022100d738ceca17a3b313e4eba2dd8b503946458ab529080284a2b0e143e110a7fd6402201211fbc3b43e83504e763a55eac5a9c524fb2a4268e44207555e026580dd91db012102506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba02483045022100c4a8c5d46a9a16740a9710df7ffd2822b5bfb0404b71ca4ea86fc71913d726c102200330a13e5a1a352c76ca3228290471524edab10c328d2db60c025820976bd43c012102506bc1dc099358e5137292f4efdd57e400f29ba5132aa5d12b18dac1c1f6aaba00000000"; Assert.assertEquals(expectedSignedHex, signResult.getSignedTx()); System.out.println(new SegWitBitcoinAddressCreator(TestNet3Params.get()).fromPrivateKey("cT4fTJyLd5RmSZFHnkGmVCzXDKuJLbyTt7cy77ghTTCagzNdPH1j")); }
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);  }