Updated on 2026-08-14
This commit is contained in:
parent
59aba143c8
commit
d36c77d0ac
13 changed files with 397 additions and 366 deletions
|
|
@ -16,7 +16,7 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 98
|
||||
versionName "0.810.1." + generateVersionName()
|
||||
versionName "0.811.1." + generateVersionName()
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
androidExtensions {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.util.Log;
|
|||
import com.tangem.App;
|
||||
import com.tangem.domain.BitcoinNode;
|
||||
import com.tangem.domain.BitcoinNodeTestNet;
|
||||
import com.tangem.domain.BitcoinCashNode;
|
||||
import com.tangem.domain.wallet.Blockchain;
|
||||
import com.tangem.domain.wallet.TangemCard;
|
||||
|
||||
|
|
@ -161,14 +162,17 @@ public class ServerApiHelperElectrum {
|
|||
}
|
||||
|
||||
private List<ElectrumRequest> doElectrumRequest(TangemCard card, ElectrumRequest electrumRequest) {
|
||||
BitcoinNode bitcoinNode = BitcoinNode.values()[new Random().nextInt(BitcoinNode.values().length)];
|
||||
|
||||
if (card.getBlockchain() == Blockchain.BitcoinTestNet || card.getBlockchain() == Blockchain.BitcoinCashTestNet) {
|
||||
if (card.getBlockchain() == Blockchain.BitcoinTestNet) {
|
||||
BitcoinNodeTestNet bitcoinNodeTestNet = BitcoinNodeTestNet.values()[new Random().nextInt(BitcoinNodeTestNet.values().length)];
|
||||
this.host = bitcoinNodeTestNet.getHost();
|
||||
this.port = bitcoinNodeTestNet.getPort();
|
||||
|
||||
} else {
|
||||
} else if (card.getBlockchain() == Blockchain.BitcoinCash) {
|
||||
BitcoinCashNode bitcoinCashNode = BitcoinCashNode.values()[new Random().nextInt(BitcoinCashNode.values().length)];
|
||||
this.host = bitcoinCashNode.getHost();
|
||||
this.port = bitcoinCashNode.getPort();
|
||||
} else if (card.getBlockchain() == Blockchain.Bitcoin) {
|
||||
BitcoinNode bitcoinNode = BitcoinNode.values()[new Random().nextInt(BitcoinNode.values().length)];
|
||||
this.host = bitcoinNode.getHost();
|
||||
this.port = bitcoinNode.getPort();
|
||||
}
|
||||
|
|
@ -178,6 +182,7 @@ public class ServerApiHelperElectrum {
|
|||
|
||||
try {
|
||||
Socket socket = App.getNetworkComponent().getSocket();
|
||||
socket.setSoTimeout(3000);
|
||||
socket.connect(new InetSocketAddress(InetAddress.getByName(host), port));
|
||||
Log.i(TAG, host + " " + port);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,22 @@
|
|||
package com.tangem.domain.wallet;
|
||||
package com.tangem.domain.wallet.BitcoinCash;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.InputFilter;
|
||||
|
||||
import com.tangem.domain.cardReader.CardProtocol;
|
||||
import com.tangem.domain.cardReader.TLV;
|
||||
import com.tangem.domain.wallet.BalanceValidator;
|
||||
import com.tangem.domain.wallet.Base58;
|
||||
import com.tangem.domain.wallet.Blockchain;
|
||||
import com.tangem.domain.wallet.BtcData;
|
||||
import com.tangem.domain.wallet.CoinData;
|
||||
import com.tangem.domain.wallet.CoinEngine;
|
||||
import com.tangem.domain.wallet.CoinEngineFactory;
|
||||
import com.tangem.domain.wallet.PINStorage;
|
||||
import com.tangem.domain.wallet.TangemCard;
|
||||
import com.tangem.domain.wallet.TangemContext;
|
||||
import com.tangem.domain.wallet.Transaction;
|
||||
import com.tangem.domain.wallet.UnspentOutputInfo;
|
||||
import com.tangem.util.BTCUtils;
|
||||
import com.tangem.util.CryptoUtil;
|
||||
import com.tangem.util.DecimalDigitsInputFilter;
|
||||
|
|
@ -113,54 +125,58 @@ public class BtcCashEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public String getFeeCurrency() {
|
||||
return "mBCH";
|
||||
return "BCH";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateAddress(String address) {
|
||||
if (address == null || address.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// if (address == null || address.isEmpty()) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (address.length() < 25) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (address.length() > 35) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// byte[] decAddress = Base58.decodeBase58(address);
|
||||
//
|
||||
// if (decAddress == null || decAddress.length == 0) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// byte[] rip = new byte[21];
|
||||
// for (int i = 0; i < 21; ++i) {
|
||||
// rip[i] = decAddress[i];
|
||||
// }
|
||||
//
|
||||
// byte[] kcv = CryptoUtil.doubleSha256(rip);
|
||||
//
|
||||
// for (int i = 0; i < 4; ++i) {
|
||||
// if (kcv[i] != decAddress[21 + i])
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (ctx.getBlockchain() != Blockchain.BitcoinTestNet && ctx.getBlockchain() != Blockchain.Bitcoin) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (ctx.getBlockchain() == Blockchain.BitcoinTestNet && (address.startsWith("1") || address.startsWith("3"))) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
|
||||
if (address.length() < 25) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.length() > 35) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!address.startsWith("1") && !address.startsWith("2") && !address.startsWith("3") && !address.startsWith("n") && !address.startsWith("m")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] decAddress = Base58.decodeBase58(address);
|
||||
|
||||
if (decAddress == null || decAddress.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] rip = new byte[21];
|
||||
for (int i = 0; i < 21; ++i) {
|
||||
rip[i] = decAddress[i];
|
||||
}
|
||||
|
||||
byte[] kcv = CryptoUtil.doubleSha256(rip);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (kcv[i] != decAddress[21 + i])
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx.getBlockchain() != Blockchain.BitcoinCashTestNet && ctx.getBlockchain() != Blockchain.BitcoinCash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx.getBlockchain() == Blockchain.BitcoinCashTestNet && (address.startsWith("1") || address.startsWith("3"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if(CashAddr.isValidCashAddress(address))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -175,7 +191,7 @@ public class BtcCashEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public Uri getShareWalletUri() {
|
||||
return Uri.parse("bitcoincash:" + ctx.getCard().getWallet());
|
||||
return Uri.parse(ctx.getCard().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -315,15 +331,17 @@ public class BtcCashEngine extends CoinEngine {
|
|||
@Override
|
||||
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
||||
byte netSelectionByte;
|
||||
switch (ctx.getBlockchain()) {
|
||||
case BitcoinCash:
|
||||
netSelectionByte = (byte) 0x00; //0 - MainNet 0x6f - TestNet
|
||||
break;
|
||||
default:
|
||||
netSelectionByte = (byte) 0x6f; //0 - MainNet 0x6f - TestNet
|
||||
break;
|
||||
}
|
||||
// CashAddr format
|
||||
byte hash1[] = Util.calculateSHA256(pkUncompressed);
|
||||
byte hash2[] = Util.calculateRIPEMD160(hash1);
|
||||
return CashAddr.toCashAddress(BitcoinCashAddressType.P2PKH, hash2);
|
||||
|
||||
}
|
||||
|
||||
public String calculateLegacyAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
||||
// Legacy format (BTC)
|
||||
byte netSelectionByte = (byte) 0x00;
|
||||
|
||||
byte hash1[] = Util.calculateSHA256(pkUncompressed);
|
||||
byte hash2[] = Util.calculateRIPEMD160(hash1);
|
||||
|
|
@ -337,7 +355,32 @@ public class BtcCashEngine extends CoinEngine {
|
|||
byte hash4[] = Util.calculateSHA256(hash3);
|
||||
|
||||
BB = ByteBuffer.allocate(hash2.length + 5);
|
||||
BB.put(netSelectionByte); //BB.put((byte) 0x6f);
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
BB.put(hash4[0]);
|
||||
BB.put(hash4[1]);
|
||||
BB.put(hash4[2]);
|
||||
BB.put(hash4[3]);
|
||||
|
||||
return org.bitcoinj.core.Base58.encode(BB.array());
|
||||
}
|
||||
|
||||
public String convertToLegacyAddress(String cashAddr) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
||||
BitcoinCashAddressDecodedParts bcadp = CashAddr.decodeCashAddress(cashAddr);
|
||||
byte netSelectionByte = (byte) 0x00;
|
||||
byte hash2[] = bcadp.hash;
|
||||
|
||||
ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1);
|
||||
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
|
||||
byte hash3[] = Util.calculateSHA256(BB.array());
|
||||
byte hash4[] = Util.calculateSHA256(hash3);
|
||||
|
||||
BB = ByteBuffer.allocate(hash2.length + 5);
|
||||
BB.put(netSelectionByte);
|
||||
BB.put(hash2);
|
||||
BB.put(hash4[0]);
|
||||
BB.put(hash4[1]);
|
||||
|
|
@ -396,16 +439,19 @@ public class BtcCashEngine extends CoinEngine {
|
|||
// }
|
||||
|
||||
@Override
|
||||
public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception {
|
||||
public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String destAddress, CardProtocol protocol) throws Exception {
|
||||
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String myAddress = ctx.getCard().getWallet();
|
||||
CoinEngine engine = CoinEngineFactory.create(ctx);
|
||||
|
||||
String srcLegacyAddress = ((BtcCashEngine)engine).convertToLegacyAddress(ctx.getCard().getWallet());
|
||||
String destLegacyAddress = ((BtcCashEngine)engine).convertToLegacyAddress(destAddress);
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
|
||||
|
||||
// Build script for our address
|
||||
List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
|
||||
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
|
||||
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(srcLegacyAddress).bytes;
|
||||
|
||||
// Collect unspent
|
||||
ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
|
||||
|
|
@ -432,7 +478,7 @@ public class BtcCashEngine extends CoinEngine {
|
|||
byte[][] dataForSign = new byte[unspentOutputs.size()][];
|
||||
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
byte[] newTX = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
|
||||
byte[] newTX = BTCUtils.buildTXForSign(srcLegacyAddress, destLegacyAddress, srcLegacyAddress, unspentOutputs, i, amount, change);
|
||||
|
||||
byte[] hashData = Util.calculateSHA256(newTX);
|
||||
byte[] doubleHashData = Util.calculateSHA256(hashData);
|
||||
|
|
@ -471,6 +517,6 @@ public class BtcCashEngine extends CoinEngine {
|
|||
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
|
||||
}
|
||||
|
||||
return BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amount, change);
|
||||
return BTCUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amount, change);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
package com.tangem.domain.wallet.BitcoinCash;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018 Tobias Brandt
|
||||
*
|
||||
* Distributed under the MIT software license, see the accompanying file LICENSE
|
||||
* or http://www.opensource.org/licenses/mit-license.php.
|
||||
*/
|
||||
|
||||
|
||||
public class CashAddr {
|
||||
|
||||
public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
|
||||
private static final char[] CHARS = CHARSET.toCharArray();
|
||||
|
||||
|
||||
public static final String SEPARATOR = ":";
|
||||
|
||||
public static final String MAIN_NET_PREFIX = "bitcoincash";
|
||||
|
||||
public static final String TEST_NET_PREFIX = "bchtest";
|
||||
|
||||
public static final String ASSUMED_DEFAULT_PREFIX = MAIN_NET_PREFIX;
|
||||
|
||||
private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] { new BigInteger("98f2bc8e61", 16),
|
||||
new BigInteger("79b76d99e2", 16), new BigInteger("f33e5fb3c4", 16), new BigInteger("ae2eabe2a8", 16),
|
||||
new BigInteger("1e4f43e470", 16) };
|
||||
|
||||
private static final BigInteger POLYMOD_AND_CONSTANT = new BigInteger("07ffffffff", 16);
|
||||
|
||||
public static String toCashAddress(BitcoinCashAddressType addressType, byte[] hash) {
|
||||
String prefixString = MAIN_NET_PREFIX;
|
||||
byte[] prefixBytes = getPrefixBytes(prefixString);
|
||||
byte[] payloadBytes = concatenateByteArrays(new byte[] { addressType.getVersionByte() }, hash);
|
||||
payloadBytes = convertBits(payloadBytes, 8, 5, false);
|
||||
byte[] allChecksumInput = concatenateByteArrays(
|
||||
concatenateByteArrays(concatenateByteArrays(prefixBytes, new byte[] { 0 }), payloadBytes),
|
||||
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
|
||||
byte[] checksumBytes = calculateChecksumBytesPolymod(allChecksumInput);
|
||||
checksumBytes = convertBits(checksumBytes, 8, 5, true);
|
||||
String cashAddress = BitcoinCashBase32.encode(concatenateByteArrays(payloadBytes, checksumBytes));
|
||||
return prefixString + SEPARATOR + cashAddress;
|
||||
}
|
||||
|
||||
public static BitcoinCashAddressDecodedParts decodeCashAddress(String bitcoinCashAddress) {
|
||||
if (!isValidCashAddress(bitcoinCashAddress)) {
|
||||
throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress);
|
||||
}
|
||||
|
||||
BitcoinCashAddressDecodedParts decoded = new BitcoinCashAddressDecodedParts();
|
||||
String[] addressParts = bitcoinCashAddress.split(SEPARATOR);
|
||||
if (addressParts.length == 2) {
|
||||
decoded.setPrefix(addressParts[0]);
|
||||
}
|
||||
|
||||
byte[] addressData = BitcoinCashBase32.decode(addressParts[1]);
|
||||
addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8);
|
||||
addressData = BitcoinCashBitArrayConverter.convertBits(addressData, 5, 8, true);
|
||||
byte versionByte = addressData[0];
|
||||
byte[] hash = Arrays.copyOfRange(addressData, 1, addressData.length);
|
||||
|
||||
decoded.setAddressType(getAddressTypeFromVersionByte(versionByte));
|
||||
decoded.setHash(hash);
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
private static BitcoinCashAddressType getAddressTypeFromVersionByte(byte versionByte) {
|
||||
for (BitcoinCashAddressType addressType : BitcoinCashAddressType.values()) {
|
||||
if (addressType.getVersionByte() == versionByte) {
|
||||
return addressType;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown version byte: " + versionByte);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean isValidCashAddress(String bitcoinCashAddress ) {
|
||||
try {
|
||||
String prefix;
|
||||
if (bitcoinCashAddress.contains(SEPARATOR)) {
|
||||
String[] split = bitcoinCashAddress.split(SEPARATOR);
|
||||
prefix = split[0];
|
||||
bitcoinCashAddress = split[1];
|
||||
} else {
|
||||
prefix =MAIN_NET_PREFIX;
|
||||
}
|
||||
|
||||
if (!isSingleCase(bitcoinCashAddress))
|
||||
return false;
|
||||
|
||||
bitcoinCashAddress = bitcoinCashAddress.toLowerCase();
|
||||
|
||||
byte[] checksumData = concatenateByteArrays(
|
||||
concatenateByteArrays(getPrefixBytes(prefix ), new byte[] { 0x00 }),
|
||||
BitcoinCashBase32.decode(bitcoinCashAddress));
|
||||
|
||||
byte[] calculateChecksumBytesPolymod = calculateChecksumBytesPolymod(checksumData);
|
||||
return new BigInteger(calculateChecksumBytesPolymod).compareTo(BigInteger.ZERO) == 0;
|
||||
} catch (RuntimeException re) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static boolean isSingleCase(String bitcoinCashAddress) {
|
||||
if (bitcoinCashAddress.equals(bitcoinCashAddress.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
if (bitcoinCashAddress.equals(bitcoinCashAddress.toUpperCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checksumInput
|
||||
* @return Returns a 40 bits checksum in form of 5 8-bit arrays. This still has
|
||||
* to me mapped to 5-bit array representation
|
||||
*/
|
||||
private static byte[] calculateChecksumBytesPolymod(byte[] checksumInput) {
|
||||
BigInteger c = BigInteger.ONE;
|
||||
|
||||
for (int i = 0; i < checksumInput.length; i++) {
|
||||
byte c0 = c.shiftRight(35).byteValue();
|
||||
c = c.and(POLYMOD_AND_CONSTANT).shiftLeft(5)
|
||||
.xor(new BigInteger(String.format("%02x", checksumInput[i]), 16));
|
||||
|
||||
if ((c0 & 0x01) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[0]);
|
||||
if ((c0 & 0x02) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[1]);
|
||||
if ((c0 & 0x04) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[2]);
|
||||
if ((c0 & 0x08) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[3]);
|
||||
if ((c0 & 0x10) != 0)
|
||||
c = c.xor(POLYMOD_GENERATORS[4]);
|
||||
}
|
||||
|
||||
byte[] checksum = c.xor(BigInteger.ONE).toByteArray();
|
||||
if (checksum.length == 5) {
|
||||
return checksum;
|
||||
} else {
|
||||
byte[] newChecksumArray = new byte[5];
|
||||
|
||||
System.arraycopy(checksum, Math.max(0, checksum.length - 5), newChecksumArray,
|
||||
Math.max(0, 5 - checksum.length), Math.min(5, checksum.length));
|
||||
|
||||
return newChecksumArray;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static byte[] getPrefixBytes(String prefixString ) {
|
||||
byte[] prefixBytes = new byte[prefixString.length()];
|
||||
|
||||
char[] charArray = prefixString.toCharArray();
|
||||
for (int i = 0; i < charArray.length; i++) {
|
||||
prefixBytes[i] = (byte) (charArray[i] & 0x1f);
|
||||
}
|
||||
|
||||
return prefixBytes;
|
||||
}
|
||||
|
||||
private static byte[] concatenateByteArrays(byte[] first, byte[] second) {
|
||||
byte[] concatenatedBytes = new byte[first.length + second.length];
|
||||
|
||||
System.arraycopy(first, 0, concatenatedBytes, 0, first.length);
|
||||
System.arraycopy(second, 0, concatenatedBytes, first.length, second.length);
|
||||
|
||||
return concatenatedBytes;
|
||||
}
|
||||
|
||||
private static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) {
|
||||
//Copyright (c) 2017 Pieter Wuille
|
||||
|
||||
int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to)
|
||||
: Math.ceil((double) bytes8Bits.length * from / to));
|
||||
int mask = ((1 << to) - 1) & 0xff;
|
||||
byte[] result = new byte[length];
|
||||
int index = 0;
|
||||
int accumulator = 0;
|
||||
int bits = 0;
|
||||
for (int i = 0; i < bytes8Bits.length; i++) {
|
||||
byte value = bytes8Bits[i];
|
||||
accumulator = (((accumulator & 0xff) << from) | (value & 0xff));
|
||||
bits += from;
|
||||
while (bits >= to) {
|
||||
bits -= to;
|
||||
result[index] = (byte) ((accumulator >> bits) & mask);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
if (!strictMode) {
|
||||
if (bits > 0) {
|
||||
result[index] = (byte) ((accumulator << (to - bits)) & mask);
|
||||
++index;
|
||||
}
|
||||
} else {
|
||||
if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) {
|
||||
throw new RuntimeException("Strict mode was used but input couldn't be converted without padding");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -13,8 +13,7 @@ public enum Blockchain {
|
|||
Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
|
||||
EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"),
|
||||
Token("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"),
|
||||
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"),
|
||||
BitcoinCashTestNet("BCH/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash Testnet");
|
||||
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash");
|
||||
|
||||
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
|
||||
mID = ID;
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ public class BtcEngine extends CoinEngine {
|
|||
@Override
|
||||
public Uri getShareWalletUri() {
|
||||
if (ctx.getCard().getDenomination() != null) {
|
||||
return Uri.parse("bitcoin:" + ctx.getCard().getWallet() + "?amount=" + BTCUtils.satoshiToBtc(ctx.getCard().getDenomination()));
|
||||
return Uri.parse("bitcoin:" + ctx.getCard().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8));
|
||||
} else {
|
||||
return Uri.parse("bitcoin:" + ctx.getCard().getWallet());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.domain.wallet;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import com.tangem.domain.wallet.BitcoinCash.BtcCashEngine;
|
||||
|
||||
/**
|
||||
* Created by Ilia on 15.02.2018.
|
||||
*/
|
||||
|
|
@ -13,7 +15,6 @@ public class CoinEngineFactory {
|
|||
case BitcoinTestNet:
|
||||
return new BtcEngine();
|
||||
case BitcoinCash:
|
||||
case BitcoinCashTestNet:
|
||||
return new BtcCashEngine();
|
||||
case Ethereum:
|
||||
case EthereumTestNet:
|
||||
|
|
@ -28,7 +29,7 @@ public class CoinEngineFactory {
|
|||
public static CoinEngine create(TangemContext context) {
|
||||
CoinEngine result;
|
||||
try {
|
||||
if (Blockchain.BitcoinCash == context.getBlockchain() || Blockchain.BitcoinCashTestNet == context.getBlockchain()) {
|
||||
if (Blockchain.BitcoinCash == context.getBlockchain()) {
|
||||
result = new BtcCashEngine(context);
|
||||
} else if (Blockchain.Bitcoin == context.getBlockchain() || Blockchain.BitcoinTestNet == context.getBlockchain()) {
|
||||
result = new BtcEngine(context);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ data class LocalStorage(
|
|||
putResourceArtworkToCatalog(R.drawable.card_ru021, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_ru022, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_ru023, true)
|
||||
putResourceArtworkToCatalog(R.drawable.card_ru024, true)
|
||||
putResourceArtworkToCatalog(R.drawable.card_ru028, true)
|
||||
putResourceArtworkToCatalog(R.drawable.card_ru029, true)
|
||||
putResourceArtworkToCatalog(R.drawable.card_ru030, true)
|
||||
}
|
||||
if (batchesFile.exists()) {
|
||||
try {
|
||||
|
|
@ -70,28 +74,6 @@ data class LocalStorage(
|
|||
} else {
|
||||
batches = HashMap()
|
||||
}
|
||||
|
||||
// if (batches.count() == 0) {
|
||||
// putBatchToCatalog("0004", R.drawable.card_ru006, false)
|
||||
// putBatchToCatalog("0006", R.drawable.card_ru006, false)
|
||||
// putBatchToCatalog("0010", R.drawable.card_ru006, false)
|
||||
//
|
||||
// putBatchToCatalog("0005", R.drawable.card_ru007, false)
|
||||
// putBatchToCatalog("0007", R.drawable.card_ru007, false)
|
||||
// putBatchToCatalog("0011", R.drawable.card_ru007, false)
|
||||
//
|
||||
// putBatchToCatalog("0012", R.drawable.card_ru011, false)
|
||||
// putBatchToCatalog("0013", R.drawable.card_ru012, false)
|
||||
// putBatchToCatalog("0014", R.drawable.card_ru006, false)
|
||||
// putBatchToCatalog("0015", R.drawable.card_ru020, false)
|
||||
// putBatchToCatalog("0016", R.drawable.card_ru021, false)
|
||||
// putBatchToCatalog("0017", R.drawable.card_ru013, false)
|
||||
// putBatchToCatalog("0019", R.drawable.card_ru016, false)
|
||||
// putBatchToCatalog("001A", R.drawable.card_ru014, false)
|
||||
// putBatchToCatalog("001B", R.drawable.card_ru015, false)
|
||||
// putBatchToCatalog("001C", R.drawable.card_ru023, false)
|
||||
// putBatchToCatalog("001D", R.drawable.card_ru022, true)
|
||||
// }
|
||||
}
|
||||
|
||||
private fun getArtworkFile(artworkId: String): File {
|
||||
|
|
@ -236,6 +218,10 @@ data class LocalStorage(
|
|||
card.batch == "001B" -> R.drawable.card_ru015
|
||||
card.batch == "001C" -> R.drawable.card_ru023
|
||||
card.batch == "001D" -> R.drawable.card_ru022
|
||||
card.batch == "001E" -> R.drawable.card_ru024
|
||||
card.batch == "001F" -> R.drawable.card_ru028
|
||||
card.batch == "0018" -> R.drawable.card_ru029
|
||||
card.batch == "0020" -> R.drawable.card_ru030
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
private lateinit var amount: CoinEngine.Amount
|
||||
|
||||
private var feeRequestSuccess = false
|
||||
private var balanceRequestSuccess = false
|
||||
// private var balanceRequestSuccess = false
|
||||
private var minFee: CoinEngine.Amount? = null
|
||||
private var maxFee: CoinEngine.Amount? = null
|
||||
private var normalFee: CoinEngine.Amount? = null
|
||||
private var isIncludeFee: Boolean = true
|
||||
private var requestPIN2Count = 0
|
||||
private var nodeCheck = false
|
||||
private var nodeCheck = true
|
||||
private var dtVerified: Date? = null
|
||||
private var calcSize: Int = 0
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
|
||||
btnSend.visibility = View.INVISIBLE
|
||||
feeRequestSuccess = false
|
||||
balanceRequestSuccess = false
|
||||
// balanceRequestSuccess = false
|
||||
|
||||
if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token) {
|
||||
rgFee.isEnabled = false
|
||||
|
|
@ -102,7 +102,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
} else {
|
||||
rgFee.isEnabled = true
|
||||
|
||||
requestElectrum(ctx.card, ElectrumRequest.checkBalance(ctx.card!!.wallet))
|
||||
// requestElectrum(ctx.card, ElectrumRequest.checkBalance(ctx.card!!.wallet))
|
||||
|
||||
calcSize = 256
|
||||
try {
|
||||
|
|
@ -194,38 +194,38 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
}
|
||||
|
||||
// request electrum listener
|
||||
val electrumBodyListener: ServerApiHelperElectrum.ElectrumRequestDataListener = object : ServerApiHelperElectrum.ElectrumRequestDataListener {
|
||||
override fun onSuccess(electrumRequest: ElectrumRequest?) {
|
||||
if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) {
|
||||
try {
|
||||
if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty))
|
||||
val engine = CoinEngineFactory.create(ctx)
|
||||
val balance = engine.convertToAmount(CoinEngine.InternalAmount(electrumRequest.result.getLong("confirmed") + electrumRequest.result.getLong("unconfirmed"), "Satoshi"))
|
||||
val amount = CoinEngine.Amount(etAmount.text.toString(), ctx.blockchain.currency)
|
||||
if (balance < amount) {
|
||||
etFee.error = getString(R.string.not_enough_funds)
|
||||
} else {
|
||||
etFee.error = null
|
||||
balanceRequestSuccess = true
|
||||
if (feeRequestSuccess && balanceRequestSuccess) {
|
||||
btnSend.visibility = View.VISIBLE
|
||||
}
|
||||
dtVerified = Date()
|
||||
nodeCheck = true
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
requestElectrum(ctx.card!!, ElectrumRequest.checkBalance(ctx.card!!.wallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFail(message: String?) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes))
|
||||
}
|
||||
|
||||
}
|
||||
serverApiHelperElectrum.setElectrumRequestData(electrumBodyListener)
|
||||
// val electrumBodyListener: ServerApiHelperElectrum.ElectrumRequestDataListener = object : ServerApiHelperElectrum.ElectrumRequestDataListener {
|
||||
// override fun onSuccess(electrumRequest: ElectrumRequest?) {
|
||||
// if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) {
|
||||
// try {
|
||||
// if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty))
|
||||
// val engine = CoinEngineFactory.create(ctx)
|
||||
// val balance = engine.convertToAmount(CoinEngine.InternalAmount(electrumRequest.result.getLong("confirmed") + electrumRequest.result.getLong("unconfirmed"), "Satoshi"))
|
||||
// val amount = CoinEngine.Amount(etAmount.text.toString(), ctx.blockchain.currency)
|
||||
// if (balance < amount) {
|
||||
// etFee.error = getString(R.string.not_enough_funds)
|
||||
// } else {
|
||||
// etFee.error = null
|
||||
// balanceRequestSuccess = true
|
||||
// if (feeRequestSuccess && balanceRequestSuccess) {
|
||||
// btnSend.visibility = View.VISIBLE
|
||||
// }
|
||||
// dtVerified = Date()
|
||||
// nodeCheck = true
|
||||
// }
|
||||
// } catch (e: JSONException) {
|
||||
// e.printStackTrace()
|
||||
//// requestElectrum(ctx.card!!, ElectrumRequest.checkBalance(ctx.card!!.wallet))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onFail(message: String?) {
|
||||
// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes))
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// serverApiHelperElectrum.setElectrumRequestData(electrumBodyListener)
|
||||
|
||||
// request infura eth gasPrice listener
|
||||
val infuraBodyListener: ServerApiHelper.InfuraBodyListener = object : ServerApiHelper.InfuraBodyListener {
|
||||
|
|
@ -252,7 +252,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
etFee.error = null
|
||||
btnSend.visibility = View.VISIBLE
|
||||
feeRequestSuccess = true
|
||||
balanceRequestSuccess = true
|
||||
// balanceRequestSuccess = true
|
||||
dtVerified = Date()
|
||||
}
|
||||
}
|
||||
|
|
@ -308,7 +308,8 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
|
||||
etFee.error = null
|
||||
feeRequestSuccess = true
|
||||
if (feeRequestSuccess && balanceRequestSuccess)
|
||||
if (feeRequestSuccess)
|
||||
// if (feeRequestSuccess && balanceRequestSuccess)
|
||||
btnSend.visibility = View.VISIBLE
|
||||
dtVerified = Date()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class PrepareCryptonitOtherAPIWithdrawalActivity : AppCompatActivity(), NfcAdapt
|
|||
Blockchain.Ethereum, Blockchain.EthereumTestNet -> {
|
||||
tvBalance.text = response.eth_available
|
||||
}
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash, Blockchain.BitcoinCashTestNet -> {
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash -> {
|
||||
tvBalance.text = response.btc_available
|
||||
}
|
||||
else -> {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
requestInfura(ServerApiHelper.INFURA_ETH_SEND_RAW_TRANSACTION, "")
|
||||
else if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet)
|
||||
requestElectrum(ctx.card!!, ElectrumRequest.broadcast(ctx.card!!.wallet, tx))
|
||||
else if (ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.BitcoinCashTestNet)
|
||||
else if (ctx.blockchain == Blockchain.BitcoinCash)
|
||||
requestElectrum(ctx.card!!, ElectrumRequest.broadcast(ctx.card!!.wallet, tx))
|
||||
|
||||
// request electrum listener
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.tangem.data.nfc.VerifyCardTask
|
|||
import com.tangem.domain.cardReader.CardProtocol
|
||||
import com.tangem.domain.cardReader.NfcManager
|
||||
import com.tangem.domain.wallet.*
|
||||
import com.tangem.domain.wallet.BitcoinCash.BtcCashEngine
|
||||
import com.tangem.presentation.activity.*
|
||||
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
|
||||
import com.tangem.presentation.dialog.PINSwapWarningDialog
|
||||
|
|
@ -811,7 +812,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
|
|||
requestVerifyAndGetInfo()
|
||||
|
||||
// Bitcoin
|
||||
if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) {
|
||||
if (ctx.blockchain == Blockchain.Bitcoin) {
|
||||
ctx.coinData.setIsBalanceEqual(true)
|
||||
|
||||
requestElectrum(ElectrumRequest.checkBalance(ctx.card!!.wallet))
|
||||
|
|
@ -820,11 +821,12 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
|
|||
}
|
||||
|
||||
// BitcoinCash
|
||||
else if (ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.BitcoinCashTestNet) {
|
||||
else if (ctx.blockchain == Blockchain.BitcoinCash) {
|
||||
ctx.coinData.setIsBalanceEqual(true)
|
||||
val engine = CoinEngineFactory.create(ctx)
|
||||
|
||||
requestElectrum(ElectrumRequest.checkBalance(ctx.card!!.wallet))
|
||||
requestElectrum(ElectrumRequest.listUnspent(ctx.card!!.wallet))
|
||||
requestElectrum(ElectrumRequest.checkBalance((engine as BtcCashEngine).convertToLegacyAddress(ctx.card!!.wallet)))
|
||||
requestElectrum(ElectrumRequest.listUnspent((engine as BtcCashEngine).convertToLegacyAddress(ctx.card!!.wallet)))
|
||||
requestRateInfo("bitcoin-cash")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,57 +37,6 @@ public final class BTCUtils {
|
|||
public static final int MAX_TX_LEN_FOR_NO_FEE = 10000;
|
||||
public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day)
|
||||
|
||||
public static long calcMinimumFee(int txLen, Collection<UnspentOutputInfo> unspentOutputInfos, long minOutput) {
|
||||
if (isZeroFeeAllowed(txLen, unspentOutputInfos, minOutput)) {
|
||||
return 0;
|
||||
}
|
||||
return MIN_FEE_PER_KB * (1 + txLen / 1000);
|
||||
}
|
||||
|
||||
public static boolean isZeroFeeAllowed(int txLen, Collection<UnspentOutputInfo> unspentOutputInfos, long minOutput) {
|
||||
if (txLen < MAX_TX_LEN_FOR_NO_FEE && minOutput > MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE) {
|
||||
long priority = 0;
|
||||
for (UnspentOutputInfo output : unspentOutputInfos) {
|
||||
if (output.confirmations > 0) {
|
||||
priority += output.confirmations * output.value;
|
||||
}
|
||||
}
|
||||
priority /= txLen;
|
||||
if (priority > MIN_PRIORITY_FOR_NO_FEE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getMaximumTxSize(Collection<UnspentOutputInfo> unspentOutputInfos, int outputsCount, boolean compressedPublicKey) throws BitcoinException {
|
||||
if (unspentOutputInfos == null || unspentOutputInfos.isEmpty()) {
|
||||
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No information about tx inputs provided");
|
||||
}
|
||||
int maxInputScriptLen = 73 + (compressedPublicKey ? 33 : 65);
|
||||
return 9 + unspentOutputInfos.size() * (41 + maxInputScriptLen) + outputsCount * 33;
|
||||
}
|
||||
|
||||
public static String publicKeyToAddress(byte[] publicKey) {
|
||||
return publicKeyToAddress(false, publicKey);
|
||||
}
|
||||
|
||||
public static String publicKeyToAddress(boolean testNet, byte[] publicKey) {
|
||||
try {
|
||||
byte[] hashedPublicKey = CryptoUtil.sha256ripemd160(publicKey);
|
||||
byte[] addressBytes = new byte[1 + hashedPublicKey.length + 4];
|
||||
addressBytes[0] = (byte) (testNet ? 111 : 0);
|
||||
System.arraycopy(hashedPublicKey, 0, addressBytes, 1, hashedPublicKey.length);
|
||||
MessageDigest digestSha = MessageDigest.getInstance("SHA-256");
|
||||
digestSha.update(addressBytes, 0, addressBytes.length - 4);
|
||||
byte[] check = digestSha.digest(digestSha.digest());
|
||||
System.arraycopy(check, 0, addressBytes, hashedPublicKey.length + 1, 4);
|
||||
return Base58.encodeBase58(addressBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toHex(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return "";
|
||||
|
|
@ -103,21 +52,6 @@ public final class BTCUtils {
|
|||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static String satoshiToBtc(byte[] satoshi)
|
||||
{
|
||||
satoshi = reverse(satoshi);
|
||||
|
||||
BigInteger num = new BigInteger(1, satoshi);
|
||||
BigDecimal dec = new BigDecimal(num);
|
||||
dec = dec.setScale(8, RoundingMode.DOWN);
|
||||
dec = dec.divide(new BigDecimal("100000000"));
|
||||
|
||||
String pattern = "#0.00000000";
|
||||
DecimalFormat myFormatter = new DecimalFormat(pattern);
|
||||
String output = myFormatter.format(dec).replace(",",".");
|
||||
return output;
|
||||
}
|
||||
|
||||
public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException {
|
||||
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
|
||||
unspentOutputs.get(currentInputPos).scriptForBuild = myScript;
|
||||
|
|
@ -207,40 +141,6 @@ public final class BTCUtils {
|
|||
return rawData;
|
||||
}
|
||||
|
||||
int calculateSize(int inputCount, int outputCount)
|
||||
{
|
||||
int size = 0;
|
||||
size += 4; // header
|
||||
size += 1; //inputCount
|
||||
|
||||
//hex str hash prev btc
|
||||
|
||||
for(int i = 0; i < inputCount; ++i)
|
||||
{
|
||||
size += 32; //prevtx
|
||||
size += 4; //outputIndex;
|
||||
size += 1; //scriptLength
|
||||
// size+=script;
|
||||
size += 4; //ffffffff
|
||||
}
|
||||
|
||||
size+=1; //outputCount
|
||||
size+=8; //amount
|
||||
size+=1;
|
||||
// size+=script;
|
||||
|
||||
if(outputCount > 1)
|
||||
{
|
||||
size+=8;
|
||||
size+=1;
|
||||
//scriptLen;
|
||||
}
|
||||
|
||||
size+=4;
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException {
|
||||
|
||||
//0200000000
|
||||
|
|
@ -297,34 +197,6 @@ public final class BTCUtils {
|
|||
return rawData;
|
||||
}
|
||||
|
||||
public static ArrayList<byte[]> getPrevTX(String hex) throws BitcoinException {
|
||||
byte[] rawTxByte = fromHex(hex);
|
||||
Transaction baseTx = new Transaction(rawTxByte);
|
||||
ArrayList<byte[]> prevHashes = new ArrayList<byte[]>();
|
||||
for(int i =0; i < baseTx.inputs.length; ++i)
|
||||
{
|
||||
Transaction.Input input = baseTx.inputs[i];
|
||||
prevHashes.add(input.outPoint.hash);
|
||||
}
|
||||
return prevHashes;
|
||||
}
|
||||
|
||||
public static boolean isInput(String myAddress, String hex) throws BitcoinException {
|
||||
byte[] rawTxByte = fromHex(hex);
|
||||
Transaction baseTx = new Transaction(rawTxByte);
|
||||
byte[] myScript = Transaction.Script.buildOutput(myAddress).bytes;
|
||||
for(int i =0; i < baseTx.inputs.length; ++i)
|
||||
{
|
||||
Transaction.Input input = baseTx.inputs[i];
|
||||
byte[] script = input.script.bytes;
|
||||
|
||||
// find outputs
|
||||
if (Arrays.equals(myScript, script)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static ArrayList<UnspentOutputInfo> getOutputs(List<BtcData.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
|
||||
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
|
||||
|
|
@ -405,107 +277,5 @@ public final class BTCUtils {
|
|||
return bytes;
|
||||
}
|
||||
|
||||
public static int findSpendableOutput(Transaction tx, String forAddress, long minAmount) throws BitcoinException {
|
||||
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(forAddress).bytes;
|
||||
int indexOfOutputToSpend = -1;
|
||||
for (int indexOfOutput = 0; indexOfOutput < tx.outputs.length; indexOfOutput++) {
|
||||
Transaction.Output output = tx.outputs[indexOfOutput];
|
||||
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
|
||||
indexOfOutputToSpend = indexOfOutput;
|
||||
break;//only one input is supported for now
|
||||
}
|
||||
}
|
||||
if (indexOfOutputToSpend == -1) {
|
||||
throw new BitcoinException(BitcoinException.ERR_NO_SPENDABLE_OUTPUTS_FOR_THE_ADDRESS, "No spendable standard outputs for " + forAddress + " have found", forAddress);
|
||||
}
|
||||
final long spendableOutputValue = tx.outputs[indexOfOutputToSpend].value;
|
||||
if (spendableOutputValue < minAmount) {
|
||||
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Unspent amount is too small: " + spendableOutputValue, spendableOutputValue);
|
||||
}
|
||||
return indexOfOutputToSpend;
|
||||
}
|
||||
|
||||
public static void verify(Transaction.Script[] scripts, Transaction spendTx) throws Transaction.Script.ScriptInvalidException {
|
||||
for (int i = 0; i < scripts.length; i++) {
|
||||
Stack<byte[]> stack = new Stack<>();
|
||||
spendTx.inputs[i].script.run(stack);//load signature+public key
|
||||
scripts[i].run(i, spendTx, stack); //verify that this transaction able to spend that output
|
||||
if (Transaction.Script.verifyFails(stack)) {
|
||||
throw new Transaction.Script.ScriptInvalidException("Signature is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class FeeChangeAndSelectedOutputs {
|
||||
public final long amountForRecipient, change, fee;
|
||||
public final ArrayList<UnspentOutputInfo> outputsToSpend;
|
||||
|
||||
public FeeChangeAndSelectedOutputs(long fee, long change, long amountForRecipient, ArrayList<UnspentOutputInfo> outputsToSpend) {
|
||||
this.fee = fee;
|
||||
this.change = change;
|
||||
this.amountForRecipient = amountForRecipient;
|
||||
this.outputsToSpend = outputsToSpend;
|
||||
}
|
||||
}
|
||||
|
||||
public static FeeChangeAndSelectedOutputs calcFeeChangeAndSelectOutputsToSpend(List<UnspentOutputInfo> unspentOutputs, long amountToSend, long extraFee, final boolean isPublicKeyCompressed) throws BitcoinException {
|
||||
long fee = 0;//calculated below
|
||||
long change = 0;
|
||||
long valueOfUnspentOutputs;
|
||||
ArrayList<UnspentOutputInfo> outputsToSpend = new ArrayList<>();
|
||||
if (amountToSend <= 0) {
|
||||
//transfer all funds from these addresses to outputAddress
|
||||
change = 0;
|
||||
valueOfUnspentOutputs = 0;
|
||||
for (UnspentOutputInfo outputInfo : unspentOutputs) {
|
||||
outputsToSpend.add(outputInfo);
|
||||
valueOfUnspentOutputs += outputInfo.value;
|
||||
}
|
||||
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, 1, isPublicKeyCompressed);
|
||||
fee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, valueOfUnspentOutputs - MIN_FEE_PER_KB * (1 + txLen / 1000));
|
||||
amountToSend = valueOfUnspentOutputs - fee - extraFee;
|
||||
} else {
|
||||
valueOfUnspentOutputs = 0;
|
||||
for (UnspentOutputInfo outputInfo : unspentOutputs) {
|
||||
outputsToSpend.add(outputInfo);
|
||||
valueOfUnspentOutputs += outputInfo.value;
|
||||
long updatedFee = MIN_FEE_PER_KB;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
fee = updatedFee;
|
||||
change = valueOfUnspentOutputs - fee - extraFee - amountToSend;
|
||||
final int txLen = BTCUtils.getMaximumTxSize(unspentOutputs, change > 0 ? 2 : 1, isPublicKeyCompressed);
|
||||
updatedFee = BTCUtils.calcMinimumFee(txLen, unspentOutputs, change > 0 ? Math.min(amountToSend, change) : amountToSend);
|
||||
if (updatedFee == fee) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fee = updatedFee;
|
||||
if (valueOfUnspentOutputs >= amountToSend + fee + extraFee) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (amountToSend > valueOfUnspentOutputs - fee) {
|
||||
throw new BitcoinException(BitcoinException.ERR_INSUFFICIENT_FUNDS, "Not enough funds", valueOfUnspentOutputs - fee);
|
||||
}
|
||||
if (outputsToSpend.isEmpty()) {
|
||||
throw new BitcoinException(BitcoinException.ERR_NO_INPUT, "No outputs to spend");
|
||||
}
|
||||
if (fee + extraFee > MAX_ALLOWED_FEE) {
|
||||
throw new BitcoinException(BitcoinException.ERR_FEE_IS_TOO_BIG, "Fee is too big", fee);
|
||||
}
|
||||
if (fee < 0 || extraFee < 0) {
|
||||
throw new BitcoinException(BitcoinException.ERR_FEE_IS_LESS_THEN_ZERO, "Incorrect fee", fee);
|
||||
}
|
||||
if (change < 0) {
|
||||
throw new BitcoinException(BitcoinException.ERR_CHANGE_IS_LESS_THEN_ZERO, "Incorrect change", change);
|
||||
}
|
||||
if (amountToSend < 0) {
|
||||
throw new BitcoinException(BitcoinException.ERR_AMOUNT_TO_SEND_IS_LESS_THEN_ZERO, "Incorrect amount to send", amountToSend);
|
||||
}
|
||||
return new FeeChangeAndSelectedOutputs(fee + extraFee, change, amountToSend, outputsToSpend);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue